@ehfuse/forma 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/hooks/useModal.d.ts +22 -2
- package/dist/esm/hooks/useModal.d.ts.map +1 -1
- package/dist/esm/hooks/useModal.js +40 -15
- package/dist/esm/hooks/useModal.js.map +1 -1
- package/dist/esm/types/modal.d.ts +13 -0
- package/dist/esm/types/modal.d.ts.map +1 -1
- package/dist/hooks/useModal.d.ts +22 -2
- package/dist/hooks/useModal.d.ts.map +1 -1
- package/dist/hooks/useModal.js +39 -14
- package/dist/hooks/useModal.js.map +1 -1
- package/dist/types/modal.d.ts +13 -0
- package/dist/types/modal.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -32,15 +32,20 @@ import { UseModalProps, UseModalReturn } from "../types/modal";
|
|
|
32
32
|
* 모바일 환경에서 모달이 열려있을 때 뒤로가기를 누르면
|
|
33
33
|
* 페이지가 뒤로 가는 것이 아니라 모달이 닫히도록 처리합니다.
|
|
34
34
|
*
|
|
35
|
+
* 같은 modalId를 사용하면 여러 컴포넌트에서 같은 모달 상태를 공유합니다.
|
|
36
|
+
*
|
|
35
37
|
* Handles modal state and back navigation.
|
|
36
38
|
* When a modal is open and user presses back button,
|
|
37
39
|
* the modal closes instead of navigating back.
|
|
38
40
|
*
|
|
39
|
-
*
|
|
41
|
+
* Using the same modalId shares modal state across multiple components.
|
|
42
|
+
*
|
|
43
|
+
* @param props - modalId: 모달 ID (같은 ID면 공유), initialOpen: 초기 열림 상태, onClose: 닫힐 때 콜백
|
|
40
44
|
* @returns isOpen, open, close, toggle, modalId
|
|
41
45
|
*
|
|
42
46
|
* @example
|
|
43
47
|
* ```tsx
|
|
48
|
+
* // 독립적인 모달 (modalId 자동 생성)
|
|
44
49
|
* function MyComponent() {
|
|
45
50
|
* const modal = useModal({
|
|
46
51
|
* onClose: () => console.log('Modal closed')
|
|
@@ -56,7 +61,22 @@ import { UseModalProps, UseModalReturn } from "../types/modal";
|
|
|
56
61
|
* </>
|
|
57
62
|
* );
|
|
58
63
|
* }
|
|
64
|
+
*
|
|
65
|
+
* // 공유 모달 (같은 modalId 사용)
|
|
66
|
+
* function ComponentA() {
|
|
67
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
68
|
+
* return <button onClick={modal.open}>Open from A</button>;
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* function ComponentB() {
|
|
72
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
73
|
+
* return (
|
|
74
|
+
* <Dialog open={modal.isOpen} onClose={modal.close}>
|
|
75
|
+
* <DialogTitle>Shared Modal</DialogTitle>
|
|
76
|
+
* </Dialog>
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
59
79
|
* ```
|
|
60
80
|
*/
|
|
61
|
-
export declare function useModal({ initialOpen, onClose: externalOnClose, }?: UseModalProps): UseModalReturn;
|
|
81
|
+
export declare function useModal({ modalId: providedModalId, initialOpen, onClose: externalOnClose, }?: UseModalProps): UseModalReturn;
|
|
62
82
|
//# sourceMappingURL=useModal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModal.d.ts","sourceRoot":"","sources":["../../../hooks/useModal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"useModal.d.ts","sourceRoot":"","sources":["../../../hooks/useModal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAW/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,QAAQ,CAAC,EACrB,OAAO,EAAE,eAAe,EACxB,WAAmB,EACnB,OAAO,EAAE,eAAe,GAC3B,GAAE,aAAkB,GAAG,cAAc,CA+GrC"}
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
* SOFTWARE.
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
28
|
+
import { useEffect, useCallback, useRef, useMemo, useContext, } from "react";
|
|
29
29
|
import { GlobalFormaContext } from "../contexts/GlobalFormaContext";
|
|
30
|
+
import { useGlobalFormaState } from "./useGlobalFormaState";
|
|
30
31
|
/**
|
|
31
32
|
* 모달 고유 ID 생성 함수
|
|
32
33
|
* Generate unique modal ID
|
|
@@ -40,15 +41,20 @@ function generateModalId() {
|
|
|
40
41
|
* 모바일 환경에서 모달이 열려있을 때 뒤로가기를 누르면
|
|
41
42
|
* 페이지가 뒤로 가는 것이 아니라 모달이 닫히도록 처리합니다.
|
|
42
43
|
*
|
|
44
|
+
* 같은 modalId를 사용하면 여러 컴포넌트에서 같은 모달 상태를 공유합니다.
|
|
45
|
+
*
|
|
43
46
|
* Handles modal state and back navigation.
|
|
44
47
|
* When a modal is open and user presses back button,
|
|
45
48
|
* the modal closes instead of navigating back.
|
|
46
49
|
*
|
|
47
|
-
*
|
|
50
|
+
* Using the same modalId shares modal state across multiple components.
|
|
51
|
+
*
|
|
52
|
+
* @param props - modalId: 모달 ID (같은 ID면 공유), initialOpen: 초기 열림 상태, onClose: 닫힐 때 콜백
|
|
48
53
|
* @returns isOpen, open, close, toggle, modalId
|
|
49
54
|
*
|
|
50
55
|
* @example
|
|
51
56
|
* ```tsx
|
|
57
|
+
* // 독립적인 모달 (modalId 자동 생성)
|
|
52
58
|
* function MyComponent() {
|
|
53
59
|
* const modal = useModal({
|
|
54
60
|
* onClose: () => console.log('Modal closed')
|
|
@@ -64,13 +70,32 @@ function generateModalId() {
|
|
|
64
70
|
* </>
|
|
65
71
|
* );
|
|
66
72
|
* }
|
|
73
|
+
*
|
|
74
|
+
* // 공유 모달 (같은 modalId 사용)
|
|
75
|
+
* function ComponentA() {
|
|
76
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
77
|
+
* return <button onClick={modal.open}>Open from A</button>;
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* function ComponentB() {
|
|
81
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
82
|
+
* return (
|
|
83
|
+
* <Dialog open={modal.isOpen} onClose={modal.close}>
|
|
84
|
+
* <DialogTitle>Shared Modal</DialogTitle>
|
|
85
|
+
* </Dialog>
|
|
86
|
+
* );
|
|
87
|
+
* }
|
|
67
88
|
* ```
|
|
68
89
|
*/
|
|
69
|
-
export function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
70
|
-
// 고유 ID 생성
|
|
71
|
-
const modalId = useMemo(() => generateModalId(), []);
|
|
72
|
-
// 모달 상태 관리
|
|
73
|
-
const
|
|
90
|
+
export function useModal({ modalId: providedModalId, initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
91
|
+
// modalId가 제공되면 사용, 아니면 고유 ID 생성
|
|
92
|
+
const modalId = useMemo(() => providedModalId || generateModalId(), [providedModalId]);
|
|
93
|
+
// 전역 상태로 모달 열림 상태 관리 (reactive!)
|
|
94
|
+
const state = useGlobalFormaState({
|
|
95
|
+
stateId: `__modal_${modalId}__`,
|
|
96
|
+
initialValues: { isOpen: initialOpen },
|
|
97
|
+
});
|
|
98
|
+
const isOpen = state.useValue("isOpen");
|
|
74
99
|
const { appendOpenModal, removeOpenModal } = useContext(GlobalFormaContext);
|
|
75
100
|
// 이미 등록된 모달인지 추적
|
|
76
101
|
const isRegisteredRef = useRef(false);
|
|
@@ -82,13 +107,13 @@ export function useModal({ initialOpen = false, onClose: externalOnClose, } = {}
|
|
|
82
107
|
// 모달 열기
|
|
83
108
|
const open = useCallback(() => {
|
|
84
109
|
if (!isOpen) {
|
|
85
|
-
|
|
110
|
+
state.setValue("isOpen", true);
|
|
86
111
|
if (!isRegisteredRef.current) {
|
|
87
112
|
appendOpenModal(modalId);
|
|
88
113
|
isRegisteredRef.current = true;
|
|
89
114
|
}
|
|
90
115
|
}
|
|
91
|
-
}, [isOpen, modalId, appendOpenModal]);
|
|
116
|
+
}, [isOpen, modalId, appendOpenModal, state]);
|
|
92
117
|
// 모달 닫기
|
|
93
118
|
const close = useCallback(() => {
|
|
94
119
|
if (isOpen) {
|
|
@@ -96,17 +121,17 @@ export function useModal({ initialOpen = false, onClose: externalOnClose, } = {}
|
|
|
96
121
|
if (isRegisteredRef.current) {
|
|
97
122
|
window.history.back();
|
|
98
123
|
// popstate 이벤트가 발생하면 FormContext의 handlePopState가 처리하여
|
|
99
|
-
// closeLastModal -> modal:close 이벤트 발생 -> handleCloseEvent 에서
|
|
124
|
+
// closeLastModal -> modal:close 이벤트 발생 -> handleCloseEvent 에서 state.setValue("isOpen", false) 호출
|
|
100
125
|
}
|
|
101
126
|
else {
|
|
102
127
|
// 등록되지 않은 경우 (open()이 호출되지 않고 직접 닫히는 경우) 직접 닫기
|
|
103
|
-
|
|
128
|
+
state.setValue("isOpen", false);
|
|
104
129
|
if (onCloseRef.current) {
|
|
105
130
|
onCloseRef.current();
|
|
106
131
|
}
|
|
107
132
|
}
|
|
108
133
|
}
|
|
109
|
-
}, [isOpen]);
|
|
134
|
+
}, [isOpen, state]);
|
|
110
135
|
// 모달 토글
|
|
111
136
|
const toggle = useCallback(() => {
|
|
112
137
|
if (isOpen) {
|
|
@@ -130,8 +155,8 @@ export function useModal({ initialOpen = false, onClose: externalOnClose, } = {}
|
|
|
130
155
|
// 외부에서 모달 닫기 요청을 처리하는 이벤트 리스너
|
|
131
156
|
const handleCloseEvent = () => {
|
|
132
157
|
// 이벤트가 수신될때는 popstate가 발생한 것이므로 히스토리는 이미 삭제된 상태라서 닫기만 실행
|
|
133
|
-
|
|
134
|
-
// 모달 제거
|
|
158
|
+
state.setValue("isOpen", false);
|
|
159
|
+
// 모달 제거
|
|
135
160
|
if (isRegisteredRef.current) {
|
|
136
161
|
removeOpenModal(modalId);
|
|
137
162
|
isRegisteredRef.current = false;
|
|
@@ -146,7 +171,7 @@ export function useModal({ initialOpen = false, onClose: externalOnClose, } = {}
|
|
|
146
171
|
return () => {
|
|
147
172
|
window.removeEventListener(`modal:close:${modalId}`, handleCloseEvent);
|
|
148
173
|
};
|
|
149
|
-
}, [modalId, removeOpenModal]);
|
|
174
|
+
}, [modalId, removeOpenModal, state]);
|
|
150
175
|
return {
|
|
151
176
|
isOpen,
|
|
152
177
|
open,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModal.js","sourceRoot":"","sources":["../../../hooks/useModal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"useModal.js","sourceRoot":"","sources":["../../../hooks/useModal.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,20 @@
|
|
|
11
11
|
* useModal 훅의 속성 인터페이스 | useModal hook properties interface
|
|
12
12
|
*/
|
|
13
13
|
export interface UseModalProps {
|
|
14
|
+
/**
|
|
15
|
+
* 모달의 고유 ID. 같은 ID를 사용하면 같은 모달 인스턴스를 공유합니다.
|
|
16
|
+
* Modal unique ID. Using the same ID shares the same modal instance.
|
|
17
|
+
*/
|
|
18
|
+
modalId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* 초기 열림 상태 (기본값: false)
|
|
21
|
+
* Initial open state (default: false)
|
|
22
|
+
*/
|
|
14
23
|
initialOpen?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 모달이 닫힐 때 호출되는 콜백
|
|
26
|
+
* Callback called when modal closes
|
|
27
|
+
*/
|
|
15
28
|
onClose?: () => void;
|
|
16
29
|
}
|
|
17
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../types/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../types/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/hooks/useModal.d.ts
CHANGED
|
@@ -32,15 +32,20 @@ import { UseModalProps, UseModalReturn } from "../types/modal";
|
|
|
32
32
|
* 모바일 환경에서 모달이 열려있을 때 뒤로가기를 누르면
|
|
33
33
|
* 페이지가 뒤로 가는 것이 아니라 모달이 닫히도록 처리합니다.
|
|
34
34
|
*
|
|
35
|
+
* 같은 modalId를 사용하면 여러 컴포넌트에서 같은 모달 상태를 공유합니다.
|
|
36
|
+
*
|
|
35
37
|
* Handles modal state and back navigation.
|
|
36
38
|
* When a modal is open and user presses back button,
|
|
37
39
|
* the modal closes instead of navigating back.
|
|
38
40
|
*
|
|
39
|
-
*
|
|
41
|
+
* Using the same modalId shares modal state across multiple components.
|
|
42
|
+
*
|
|
43
|
+
* @param props - modalId: 모달 ID (같은 ID면 공유), initialOpen: 초기 열림 상태, onClose: 닫힐 때 콜백
|
|
40
44
|
* @returns isOpen, open, close, toggle, modalId
|
|
41
45
|
*
|
|
42
46
|
* @example
|
|
43
47
|
* ```tsx
|
|
48
|
+
* // 독립적인 모달 (modalId 자동 생성)
|
|
44
49
|
* function MyComponent() {
|
|
45
50
|
* const modal = useModal({
|
|
46
51
|
* onClose: () => console.log('Modal closed')
|
|
@@ -56,7 +61,22 @@ import { UseModalProps, UseModalReturn } from "../types/modal";
|
|
|
56
61
|
* </>
|
|
57
62
|
* );
|
|
58
63
|
* }
|
|
64
|
+
*
|
|
65
|
+
* // 공유 모달 (같은 modalId 사용)
|
|
66
|
+
* function ComponentA() {
|
|
67
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
68
|
+
* return <button onClick={modal.open}>Open from A</button>;
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* function ComponentB() {
|
|
72
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
73
|
+
* return (
|
|
74
|
+
* <Dialog open={modal.isOpen} onClose={modal.close}>
|
|
75
|
+
* <DialogTitle>Shared Modal</DialogTitle>
|
|
76
|
+
* </Dialog>
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
59
79
|
* ```
|
|
60
80
|
*/
|
|
61
|
-
export declare function useModal({ initialOpen, onClose: externalOnClose, }?: UseModalProps): UseModalReturn;
|
|
81
|
+
export declare function useModal({ modalId: providedModalId, initialOpen, onClose: externalOnClose, }?: UseModalProps): UseModalReturn;
|
|
62
82
|
//# sourceMappingURL=useModal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModal.d.ts","sourceRoot":"","sources":["../../hooks/useModal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"useModal.d.ts","sourceRoot":"","sources":["../../hooks/useModal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAW/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,QAAQ,CAAC,EACrB,OAAO,EAAE,eAAe,EACxB,WAAmB,EACnB,OAAO,EAAE,eAAe,GAC3B,GAAE,aAAkB,GAAG,cAAc,CA+GrC"}
|
package/dist/hooks/useModal.js
CHANGED
|
@@ -30,6 +30,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
30
30
|
exports.useModal = useModal;
|
|
31
31
|
const react_1 = require("react");
|
|
32
32
|
const GlobalFormaContext_1 = require("../contexts/GlobalFormaContext");
|
|
33
|
+
const useGlobalFormaState_1 = require("./useGlobalFormaState");
|
|
33
34
|
/**
|
|
34
35
|
* 모달 고유 ID 생성 함수
|
|
35
36
|
* Generate unique modal ID
|
|
@@ -43,15 +44,20 @@ function generateModalId() {
|
|
|
43
44
|
* 모바일 환경에서 모달이 열려있을 때 뒤로가기를 누르면
|
|
44
45
|
* 페이지가 뒤로 가는 것이 아니라 모달이 닫히도록 처리합니다.
|
|
45
46
|
*
|
|
47
|
+
* 같은 modalId를 사용하면 여러 컴포넌트에서 같은 모달 상태를 공유합니다.
|
|
48
|
+
*
|
|
46
49
|
* Handles modal state and back navigation.
|
|
47
50
|
* When a modal is open and user presses back button,
|
|
48
51
|
* the modal closes instead of navigating back.
|
|
49
52
|
*
|
|
50
|
-
*
|
|
53
|
+
* Using the same modalId shares modal state across multiple components.
|
|
54
|
+
*
|
|
55
|
+
* @param props - modalId: 모달 ID (같은 ID면 공유), initialOpen: 초기 열림 상태, onClose: 닫힐 때 콜백
|
|
51
56
|
* @returns isOpen, open, close, toggle, modalId
|
|
52
57
|
*
|
|
53
58
|
* @example
|
|
54
59
|
* ```tsx
|
|
60
|
+
* // 독립적인 모달 (modalId 자동 생성)
|
|
55
61
|
* function MyComponent() {
|
|
56
62
|
* const modal = useModal({
|
|
57
63
|
* onClose: () => console.log('Modal closed')
|
|
@@ -67,13 +73,32 @@ function generateModalId() {
|
|
|
67
73
|
* </>
|
|
68
74
|
* );
|
|
69
75
|
* }
|
|
76
|
+
*
|
|
77
|
+
* // 공유 모달 (같은 modalId 사용)
|
|
78
|
+
* function ComponentA() {
|
|
79
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
80
|
+
* return <button onClick={modal.open}>Open from A</button>;
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* function ComponentB() {
|
|
84
|
+
* const modal = useModal({ modalId: 'shared-modal' });
|
|
85
|
+
* return (
|
|
86
|
+
* <Dialog open={modal.isOpen} onClose={modal.close}>
|
|
87
|
+
* <DialogTitle>Shared Modal</DialogTitle>
|
|
88
|
+
* </Dialog>
|
|
89
|
+
* );
|
|
90
|
+
* }
|
|
70
91
|
* ```
|
|
71
92
|
*/
|
|
72
|
-
function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
73
|
-
// 고유 ID 생성
|
|
74
|
-
const modalId = (0, react_1.useMemo)(() => generateModalId(), []);
|
|
75
|
-
// 모달 상태 관리
|
|
76
|
-
const
|
|
93
|
+
function useModal({ modalId: providedModalId, initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
94
|
+
// modalId가 제공되면 사용, 아니면 고유 ID 생성
|
|
95
|
+
const modalId = (0, react_1.useMemo)(() => providedModalId || generateModalId(), [providedModalId]);
|
|
96
|
+
// 전역 상태로 모달 열림 상태 관리 (reactive!)
|
|
97
|
+
const state = (0, useGlobalFormaState_1.useGlobalFormaState)({
|
|
98
|
+
stateId: `__modal_${modalId}__`,
|
|
99
|
+
initialValues: { isOpen: initialOpen },
|
|
100
|
+
});
|
|
101
|
+
const isOpen = state.useValue("isOpen");
|
|
77
102
|
const { appendOpenModal, removeOpenModal } = (0, react_1.useContext)(GlobalFormaContext_1.GlobalFormaContext);
|
|
78
103
|
// 이미 등록된 모달인지 추적
|
|
79
104
|
const isRegisteredRef = (0, react_1.useRef)(false);
|
|
@@ -85,13 +110,13 @@ function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
|
85
110
|
// 모달 열기
|
|
86
111
|
const open = (0, react_1.useCallback)(() => {
|
|
87
112
|
if (!isOpen) {
|
|
88
|
-
|
|
113
|
+
state.setValue("isOpen", true);
|
|
89
114
|
if (!isRegisteredRef.current) {
|
|
90
115
|
appendOpenModal(modalId);
|
|
91
116
|
isRegisteredRef.current = true;
|
|
92
117
|
}
|
|
93
118
|
}
|
|
94
|
-
}, [isOpen, modalId, appendOpenModal]);
|
|
119
|
+
}, [isOpen, modalId, appendOpenModal, state]);
|
|
95
120
|
// 모달 닫기
|
|
96
121
|
const close = (0, react_1.useCallback)(() => {
|
|
97
122
|
if (isOpen) {
|
|
@@ -99,17 +124,17 @@ function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
|
99
124
|
if (isRegisteredRef.current) {
|
|
100
125
|
window.history.back();
|
|
101
126
|
// popstate 이벤트가 발생하면 FormContext의 handlePopState가 처리하여
|
|
102
|
-
// closeLastModal -> modal:close 이벤트 발생 -> handleCloseEvent 에서
|
|
127
|
+
// closeLastModal -> modal:close 이벤트 발생 -> handleCloseEvent 에서 state.setValue("isOpen", false) 호출
|
|
103
128
|
}
|
|
104
129
|
else {
|
|
105
130
|
// 등록되지 않은 경우 (open()이 호출되지 않고 직접 닫히는 경우) 직접 닫기
|
|
106
|
-
|
|
131
|
+
state.setValue("isOpen", false);
|
|
107
132
|
if (onCloseRef.current) {
|
|
108
133
|
onCloseRef.current();
|
|
109
134
|
}
|
|
110
135
|
}
|
|
111
136
|
}
|
|
112
|
-
}, [isOpen]);
|
|
137
|
+
}, [isOpen, state]);
|
|
113
138
|
// 모달 토글
|
|
114
139
|
const toggle = (0, react_1.useCallback)(() => {
|
|
115
140
|
if (isOpen) {
|
|
@@ -133,8 +158,8 @@ function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
|
133
158
|
// 외부에서 모달 닫기 요청을 처리하는 이벤트 리스너
|
|
134
159
|
const handleCloseEvent = () => {
|
|
135
160
|
// 이벤트가 수신될때는 popstate가 발생한 것이므로 히스토리는 이미 삭제된 상태라서 닫기만 실행
|
|
136
|
-
|
|
137
|
-
// 모달 제거
|
|
161
|
+
state.setValue("isOpen", false);
|
|
162
|
+
// 모달 제거
|
|
138
163
|
if (isRegisteredRef.current) {
|
|
139
164
|
removeOpenModal(modalId);
|
|
140
165
|
isRegisteredRef.current = false;
|
|
@@ -149,7 +174,7 @@ function useModal({ initialOpen = false, onClose: externalOnClose, } = {}) {
|
|
|
149
174
|
return () => {
|
|
150
175
|
window.removeEventListener(`modal:close:${modalId}`, handleCloseEvent);
|
|
151
176
|
};
|
|
152
|
-
}, [modalId, removeOpenModal]);
|
|
177
|
+
}, [modalId, removeOpenModal, state]);
|
|
153
178
|
return {
|
|
154
179
|
isOpen,
|
|
155
180
|
open,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModal.js","sourceRoot":"","sources":["../../hooks/useModal.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;
|
|
1
|
+
{"version":3,"file":"useModal.js","sourceRoot":"","sources":["../../hooks/useModal.ts"],"names":[],"mappings":"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"}
|
package/dist/types/modal.d.ts
CHANGED
|
@@ -11,7 +11,20 @@
|
|
|
11
11
|
* useModal 훅의 속성 인터페이스 | useModal hook properties interface
|
|
12
12
|
*/
|
|
13
13
|
export interface UseModalProps {
|
|
14
|
+
/**
|
|
15
|
+
* 모달의 고유 ID. 같은 ID를 사용하면 같은 모달 인스턴스를 공유합니다.
|
|
16
|
+
* Modal unique ID. Using the same ID shares the same modal instance.
|
|
17
|
+
*/
|
|
18
|
+
modalId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* 초기 열림 상태 (기본값: false)
|
|
21
|
+
* Initial open state (default: false)
|
|
22
|
+
*/
|
|
14
23
|
initialOpen?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 모달이 닫힐 때 호출되는 콜백
|
|
26
|
+
* Callback called when modal closes
|
|
27
|
+
*/
|
|
15
28
|
onClose?: () => void;
|
|
16
29
|
}
|
|
17
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../types/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../types/modal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ehfuse/forma",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Advanced React state management library with individual field subscriptions - supports both forms and general state management with useFormaState",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|