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.
- package/README.md +157 -0
- package/dist/index.cjs +912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +618 -0
- package/dist/index.d.ts +618 -0
- package/dist/index.js +874 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
- package/src/adapters.ts +567 -0
- package/src/api.ts +418 -0
- package/src/context.tsx +206 -0
- package/src/core.ts +226 -0
- package/src/hoc.tsx +114 -0
- package/src/hooks.ts +250 -0
- package/src/index.ts +81 -0
- package/src/modal-manager.ts +136 -0
- package/src/types.ts +243 -0
|
@@ -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
|
+
}
|