modalio 0.9.0

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.
@@ -0,0 +1,136 @@
1
+ import {
2
+ cleanupAllModals,
3
+ cleanupModal,
4
+ closeAllModals,
5
+ closeModal,
6
+ getOpenModals,
7
+ hasOpenModals,
8
+ openModal,
9
+ removeModal,
10
+ } from "./api";
11
+ import { ModalDef, ModalProvider } from "./context";
12
+ import { register, unregister } from "./core";
13
+ import { createModal } from "./hoc";
14
+
15
+ /**
16
+ * ModalManager namespace - the primary API for managing modals.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * import { ModalManager } from "modalio";
21
+ *
22
+ * // Create a modal
23
+ * const MyModal = ModalManager.create<{ message: string }>(({ message }) => {
24
+ * const modal = useModal();
25
+ * return (
26
+ * <Dialog {...radixDialog(modal)}>
27
+ * <DialogContent {...radixDialogContent(modal)}>
28
+ * <p>{message}</p>
29
+ * <button onClick={() => modal.close(true)}>Confirm</button>
30
+ * </DialogContent>
31
+ * </Dialog>
32
+ * );
33
+ * });
34
+ *
35
+ * // Open it
36
+ * const result = await ModalManager.open(MyModal, {
37
+ * data: { message: "Hello!" }
38
+ * }).afterClosed();
39
+ *
40
+ * // Close it
41
+ * ModalManager.close(MyModal);
42
+ *
43
+ * // Close all modals
44
+ * ModalManager.closeAll();
45
+ * ```
46
+ */
47
+ export const ModalManager = {
48
+ /**
49
+ * Create a modal component with lifecycle management.
50
+ * Wraps your component with automatic registration and state handling.
51
+ */
52
+ create: createModal,
53
+
54
+ /**
55
+ * Open a modal and return a ModalRef for controlling it.
56
+ * @returns ModalRef with afterOpened(), afterClosed(), beforeClosed() promises
57
+ */
58
+ open: openModal,
59
+
60
+ /**
61
+ * Close a specific modal by component or ID.
62
+ * @returns Promise that resolves when the close animation completes
63
+ */
64
+ close: closeModal,
65
+
66
+ /**
67
+ * Close all open modals.
68
+ * @returns Promise that resolves when all close animations complete
69
+ */
70
+ closeAll: closeAllModals,
71
+
72
+ /**
73
+ * Remove a modal from the DOM completely.
74
+ */
75
+ remove: removeModal,
76
+
77
+ /**
78
+ * Check if any modals are currently open.
79
+ */
80
+ hasOpen: hasOpenModals,
81
+
82
+ /**
83
+ * Get array of currently open modal IDs.
84
+ */
85
+ getOpen: getOpenModals,
86
+
87
+ /**
88
+ * Register a modal component with an ID for later use.
89
+ */
90
+ register,
91
+
92
+ /**
93
+ * Unregister a previously registered modal.
94
+ */
95
+ unregister,
96
+
97
+ /**
98
+ * Clean up all internal state for a specific modal.
99
+ */
100
+ cleanup: cleanupModal,
101
+
102
+ /**
103
+ * Clean up all modal state (useful for testing).
104
+ */
105
+ cleanupAll: cleanupAllModals,
106
+
107
+ /**
108
+ * Context provider for modal management.
109
+ * Must wrap your application.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * function App() {
114
+ * return (
115
+ * <ModalManager.Provider>
116
+ * <YourApp />
117
+ * </ModalManager.Provider>
118
+ * );
119
+ * }
120
+ * ```
121
+ */
122
+ Provider: ModalProvider,
123
+
124
+ /**
125
+ * Declaratively define a modal in JSX.
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * <ModalManager.Def component={MyModal} id="my-modal" />
130
+ *
131
+ * // Later: open by ID
132
+ * ModalManager.open("my-modal");
133
+ * ```
134
+ */
135
+ Def: ModalDef,
136
+ } as const;
package/src/types.ts ADDED
@@ -0,0 +1,243 @@
1
+ import type {
2
+ ComponentType,
3
+ Dispatch,
4
+ JSXElementConstructor,
5
+ ReactNode,
6
+ } from "react";
7
+
8
+ // =============================================================================
9
+ // Internal Types
10
+ // =============================================================================
11
+
12
+ /** Internal key for modal configuration stored in data */
13
+ export const MODAL_CONFIG_KEY = "__modalConfig" as const;
14
+
15
+ /** Internal modal configuration shape */
16
+ export interface InternalModalConfig {
17
+ disableClose?: boolean;
18
+ keepMounted?: boolean;
19
+ }
20
+
21
+ // =============================================================================
22
+ // State Types
23
+ // =============================================================================
24
+
25
+ /**
26
+ * State for a single modal instance
27
+ */
28
+ export interface ModalState {
29
+ modalId: string;
30
+ data?: Record<string, unknown>;
31
+ isOpen?: boolean;
32
+ delayOpen?: boolean;
33
+ keepMounted?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Global modal store - maps modal IDs to their state
38
+ */
39
+ export interface ModalStore {
40
+ [key: string]: ModalState | undefined;
41
+ }
42
+
43
+ /**
44
+ * Modal lifecycle state
45
+ */
46
+ export type ModalLifecycleState = "open" | "closing" | "closed";
47
+
48
+ // =============================================================================
49
+ // Action Types (for Redux/external state management)
50
+ // =============================================================================
51
+
52
+ export type ModalActionType = "show" | "hide" | "remove" | "set-flags";
53
+
54
+ export interface ModalAction {
55
+ type: `modalio/${ModalActionType}`;
56
+ payload: {
57
+ modalId: string;
58
+ data?: Record<string, unknown>;
59
+ flags?: Record<string, unknown>;
60
+ };
61
+ }
62
+
63
+ // =============================================================================
64
+ // Promise/Callback Types
65
+ // =============================================================================
66
+
67
+ /**
68
+ * Deferred promise with exposed resolve/reject handlers
69
+ */
70
+ export interface DeferredPromise<T = unknown> {
71
+ resolve: (value: T) => void;
72
+ reject: (reason: unknown) => void;
73
+ promise: Promise<T>;
74
+ }
75
+
76
+ // =============================================================================
77
+ // Modal Handler Types (returned by useModal)
78
+ // =============================================================================
79
+
80
+ /**
81
+ * Read-only state of a modal instance
82
+ */
83
+ export interface ModalReadState<TData = Record<string, unknown>> {
84
+ /** The modal's unique identifier */
85
+ readonly modalId: string;
86
+ /** Data passed to the modal via open() */
87
+ readonly data: TData | undefined;
88
+ /** Whether the modal is currently open */
89
+ readonly isOpen: boolean;
90
+ /** Whether to keep the modal mounted after closing */
91
+ readonly keepMounted: boolean;
92
+ }
93
+
94
+ /**
95
+ * Methods for controlling a modal
96
+ */
97
+ export interface ModalControls<TData = Record<string, unknown>> {
98
+ /** Open the modal with optional data */
99
+ open: (data?: TData) => Promise<unknown>;
100
+ /** Close the modal and resolve with a result */
101
+ close: (result?: unknown) => void;
102
+ /** Dismiss the modal (close without result, resolves undefined) */
103
+ dismiss: () => void;
104
+ /** Remove the modal from the DOM immediately */
105
+ remove: () => void;
106
+ }
107
+
108
+ /**
109
+ * Internal methods for animation lifecycle (used by adapters)
110
+ * @internal
111
+ */
112
+ export interface ModalAnimationHandlers {
113
+ /** Called when closing animation completes */
114
+ onAnimationEnd: () => void;
115
+ }
116
+
117
+ /**
118
+ * Complete modal handler returned by useModal hook
119
+ * Combines state, controls, and animation handlers
120
+ */
121
+ export interface ModalHandler<TData = Record<string, unknown>>
122
+ extends ModalReadState<TData>,
123
+ ModalControls<TData>,
124
+ ModalAnimationHandlers {}
125
+
126
+ // =============================================================================
127
+ // HOC Types
128
+ // =============================================================================
129
+
130
+ /**
131
+ * Props injected into HOC-wrapped modal components
132
+ * Note: Uses `modalId` instead of `id` to avoid conflicts with common props
133
+ */
134
+ export interface ModalHocProps {
135
+ modalId: string;
136
+ defaultOpen?: boolean;
137
+ keepMounted?: boolean;
138
+ }
139
+
140
+ /**
141
+ * Registry entry for a modal component
142
+ */
143
+ export interface ModalRegistryEntry<
144
+ TProps extends Record<string, unknown> = Record<string, unknown>,
145
+ > {
146
+ comp: ComponentType<ModalHocProps & TProps>;
147
+ props?: TProps;
148
+ }
149
+
150
+ /**
151
+ * Extract props from a component type, excluding HOC props
152
+ */
153
+ export type ModalProps<T> =
154
+ T extends JSXElementConstructor<infer P>
155
+ ? Omit<P, keyof ModalHocProps>
156
+ : Record<string, unknown>;
157
+
158
+ // =============================================================================
159
+ // Provider Types
160
+ // =============================================================================
161
+
162
+ /**
163
+ * Props for the ModalProvider component
164
+ */
165
+ export interface ModalProviderProps {
166
+ children?: ReactNode;
167
+ /** Optional external dispatch for state management integration */
168
+ dispatch?: Dispatch<ModalAction>;
169
+ /** Optional external modal store */
170
+ modals?: ModalStore;
171
+ }
172
+
173
+ // =============================================================================
174
+ // Modal Reference Type (returned by open())
175
+ // =============================================================================
176
+
177
+ /**
178
+ * Modal reference for programmatic control
179
+ * Provides lifecycle promises and control methods
180
+ */
181
+ export interface ModalRef<TResult = unknown, TData = unknown> {
182
+ /** Unique identifier for this modal instance */
183
+ readonly modalId: string;
184
+ /** Data passed to the modal */
185
+ readonly data: TData | undefined;
186
+ /** Whether the user is allowed to close the modal (via escape/outside click) */
187
+ disableClose: boolean;
188
+ /** Close the modal with an optional result */
189
+ close: (result?: TResult) => void;
190
+ /** Promise that resolves when the modal opening animation completes */
191
+ afterOpened: () => Promise<void>;
192
+ /** Promise that resolves with the result when the modal closing animation completes */
193
+ afterClosed: () => Promise<TResult | undefined>;
194
+ /** Promise that resolves when the modal starts closing (before animation) */
195
+ beforeClosed: () => Promise<TResult | undefined>;
196
+ /** Update the data passed to the modal */
197
+ updateData: (data: Partial<TData>) => void;
198
+ /** Get the current lifecycle state of the modal */
199
+ getState: () => ModalLifecycleState;
200
+ }
201
+
202
+ /**
203
+ * Configuration for opening a modal
204
+ */
205
+ export interface ModalConfig<TData = unknown> {
206
+ /** Data to pass to the modal component */
207
+ readonly data?: TData;
208
+ /** Keep the modal mounted in DOM after closing (for animation performance) */
209
+ readonly keepMounted?: boolean;
210
+ /** Custom ID for the modal (auto-generated if not provided) */
211
+ readonly modalId?: string;
212
+ /** Whether clicking outside/escape closes the modal */
213
+ readonly disableClose?: boolean;
214
+ }
215
+
216
+ // =============================================================================
217
+ // Adapter Types (for UI library integration)
218
+ // =============================================================================
219
+
220
+ /**
221
+ * Props for Radix UI Dialog root component
222
+ */
223
+ export interface RadixDialogProps {
224
+ open: boolean;
225
+ onOpenChange: (open: boolean) => void;
226
+ }
227
+
228
+ /**
229
+ * Props for Radix UI Dialog content component
230
+ */
231
+ export interface RadixDialogContentProps {
232
+ onAnimationEndCapture: () => void;
233
+ onEscapeKeyDown: (e?: Event) => void;
234
+ onPointerDownOutside: (e?: Event) => void;
235
+ }
236
+
237
+ /**
238
+ * Combined props for Shadcn Dialog
239
+ */
240
+ export interface ShadcnDialogProps extends RadixDialogProps {
241
+ onClose: () => void;
242
+ onAnimationEndCapture: () => void;
243
+ }