modalyze 1.0.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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+
2
+
3
+ Copyright 2026 Kaundur
4
+
5
+ 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:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ 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.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # Modalyze
2
+
3
+ **Context-aware modals for React**
4
+
5
+ A React library for creating draggable, resizable, stackable modals that preserve React context. Create modals programmatically without losing access to your context providers.
6
+
7
+ ## Features
8
+
9
+ - **Context Preservation** - Modals maintain access to React context
10
+ - **Programmatic API** - Create modals by calling `createModal()`
11
+ - **Multiple Modals** - Open and manage many modals at once
12
+ - **Draggable & Resizable** - Move and resize modals freely
13
+ - **Zero Config** - Drop in and use immediately
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install modalyze
19
+ # or
20
+ yarn add modalyze
21
+ # or
22
+ pnpm add modalyze
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ **1. Add the `<Modalyze>` provider to your app**
28
+
29
+ Place it below any context providers you want modals to access:
30
+
31
+ ```tsx
32
+ import { Modalyze } from 'modalyze';
33
+
34
+ function App() {
35
+ return (
36
+ <OtherProvider>
37
+ <Modalyze>
38
+ <BasicExample /> {/* Modals created here access OtherProvider */}
39
+ </Modalyze>
40
+ </OtherProvider>
41
+ );
42
+ }
43
+ ```
44
+
45
+ **2. Create modals from any component**
46
+
47
+ Use the `useModalyze()` hook to create modals:
48
+
49
+ ```tsx
50
+ import { useModalyze } from 'modalyze';
51
+
52
+ const BasicModal = () => {
53
+ return <div>Basic Modal</div>;
54
+ };
55
+
56
+ const BasicExample = () => {
57
+ const { createModal } = useModalyze();
58
+ return <button onClick={() => createModal(BasicModal)}>Open Modal</button>;
59
+ };
60
+ ```
61
+
62
+ That's it! Modals will have access to any context providers above the `<Modalyze>` component.
63
+
64
+ ## Usage Guide
65
+
66
+ ### Creating and Closing Modals
67
+
68
+ #### How do I create a modal?
69
+
70
+ Call the `createModal()` function from the `useModalyze()` hook, `const {createModal} = useModalyze()`. The `<Modalyze>` component must also be included somewhere in your app
71
+
72
+ #### How do I close a modal?
73
+
74
+ Modals can be closed via ESC key, clicking outside, or both. Default is ESC only.
75
+
76
+ ```ts
77
+ createModal(BasicModal, { closeOnEscape: true, closeOnOutsideClick: false });
78
+ ```
79
+
80
+ From within a modal
81
+
82
+ ```ts
83
+ const { close } = useModalyzeModal();
84
+ ```
85
+
86
+ From outside a modal with the modal ID
87
+
88
+ ```ts
89
+ const { closeModal } = useModalyze();
90
+ closeModal(modalId);
91
+ ```
92
+
93
+ Or close all modals
94
+
95
+ ```ts
96
+ const { closeAllModals } = useModalyze();
97
+ closeAllModals();
98
+ ```
99
+
100
+ ### Working with Context
101
+
102
+ #### How do I access context within a modal?
103
+
104
+ Just use hooks normally, context is preserved. The modal accesses context from the nearest parent `<Modalyze/>` provider from where `createModal()` is called
105
+
106
+ #### How do I share state between the page and modal?
107
+
108
+ Put the state within a context, access it from both places
109
+
110
+ #### Why aren't my props updating in the modal?
111
+
112
+ Props passed via `createModal()` are captured at creation time and won't update. For live data that changes over time, use React context instead. Context values will update reactively in your modal.
113
+
114
+ ### Advanced Scenarios
115
+
116
+ #### How do I close a specific modal programmatically?
117
+
118
+ Save the ID: `const id = createModal(...)` then `closeModal(id)`
119
+
120
+ #### How do I handle multiple context providers?
121
+
122
+ Place a `<Modalyze>` within each provider level, context is preserved at the nearest parent `<Modalyze>` from which createModal is called
123
+
124
+ ```tsx
125
+ return (
126
+ <div>
127
+ <OuterProvider>
128
+ <Modalyze>
129
+ <MainContainer />
130
+ <InnerProvider>
131
+ <Modalyze>
132
+ <InnerContainer />
133
+ </Modalyze>
134
+ </InnerProvider>
135
+ <InnerProvider>
136
+ <Modalyze>
137
+ <InnerContainer />
138
+ </Modalyze>
139
+ </InnerProvider>
140
+ </Modalyze>
141
+ </OuterProvider>
142
+ </div>
143
+ );
144
+ ```
145
+
146
+ #### How does context capture work?
147
+
148
+ 1. `createModal()` creates the modal element at the nearest `<Modalyze>` parent, capturing context at that level
149
+ 2. The modal is portaled up to the root `<Modalyze>` component
150
+ 3. The root renders all modals in a shared stack, enabling proper ordering while preserving each modal's captured context
151
+
152
+ #### Can I have multiple `<Modalyze>` roots?
153
+
154
+ Yes, but **not recommended**. Multiple root `<Modalyze>` components create separate modal stacks that can't be ordered relative to each other, one stack will always render above the other.
155
+
156
+ **Recommended:** Use a single root `<Modalyze>` component, with nested `<Modalyze>` providers only for capturing different context levels.
157
+
158
+ #### How can I prevent a modal from closing?
159
+
160
+ Use the `closeOnEscape` and `closeOnOutsideClick` options to prevent dismissing.
161
+
162
+ If you need to intercept the close request use the `setCloseRequestHandler` or `setModalCloseRequestHandler` depending
163
+ on if you're setting it from inside or outside the modal.
164
+
165
+ ## API Reference
166
+
167
+ ### Modalyze Component `<Modalyze>`
168
+
169
+ The main component. Must be included once to create modals, can be repeated to capture different context levels, but should only be one root `<Modalyze>`
170
+
171
+ ### Modalyze Hook `useModalyze()`
172
+
173
+ Functions
174
+
175
+ - `createModal<P>(component: ModalComponent<P>, options?: ModalCreationOptions<P>): string` - Creates a modal imperatively while preserving React context. Returns the modal ID.
176
+ - `closeModal(modalId: string): void` - Closes a specific modal by ID
177
+ - `closeAllModals(): void` - Closes all open modals
178
+ - `setModalCloseRequestHandler(modalId: string, handler: ModalCloseHandler | null): void` - Sets a handler to intercept close requests. Return `false` to prevent closure. Pass `null` to remove the handler.
179
+ - `setFocusedModal(modalId?: string): void` - Focuses the specified modal, or clears focus if no ID provided
180
+ - `bringModalToFront(modalId: string): void` - Brings the specified modal to the top of the stack
181
+
182
+ Observable State
183
+
184
+ - `modalIds: string[]` - Array of all modal IDs in stack order (bottom to top)
185
+ - `modalCount: number` - Total number of open modals
186
+ - `focusedModalId: string | null` - ID of the currently focused modal
187
+ - `frontModalId: string | null` - ID of the topmost modal in the stack
188
+
189
+ ### ModalyzeModal hook `useModalyzeModal()`
190
+
191
+ Functions
192
+
193
+ - `close()` - Close this modal
194
+ - `setCloseRequestHandler(handler: ModalCloseHandler | null): void` - Sets a handler to intercept close requests. Return `false` to prevent closure. Pass `null` to remove handler.
195
+ - `setSize(width: number, height: number): {width: number, height: number} | null` - Set the size of the current modal, returns the updated size
196
+ - `setPosition(x: number, y: number): {x: number, y: number} | null` - Set the position of the current modal, returns the updated position
197
+
198
+ Observable State
199
+
200
+ - `modalId: string` - ID for this modal
201
+ - `isFocusedModal: boolean` - Whether this modal is currently focused
202
+ - `isTopModal: boolean` - Whether this modal is at the top of the stack
203
+
204
+ ## Types
205
+
206
+ ### Modal Configuration
207
+
208
+ **`ModalBehaviorConfig`**
209
+
210
+ ```ts
211
+ export type ModalBehaviorConfig = {
212
+ closeOnEscape?: boolean;
213
+ closeOnOutsideClick?: boolean;
214
+ minSize?: { width: number; height: number };
215
+ position?: { x: number; y: number };
216
+ };
217
+ ```
218
+
219
+ **`ModalConfig`**
220
+
221
+ ```ts
222
+ export type ModalConfig = ModalBehaviorConfig & {
223
+ title?: string;
224
+ size?: { width: number; height: number };
225
+ };
226
+ ```
227
+
228
+ **`ModalCreationOptions<P>`**
229
+
230
+ ```ts
231
+ export type ModalCreationOptions<P = Record<string, unknown>> = ModalConfig & {
232
+ props?: P;
233
+ };
234
+ ```
235
+
236
+ ### Close Request Handling
237
+
238
+ **`ModalyzeCloseRequestEvent`**
239
+
240
+ ```ts
241
+ export interface ModalyzeCloseRequestEvent {
242
+ reason: 'escape' | 'outside' | 'manual';
243
+ nativeEvent?: MouseEvent | TouchEvent | KeyboardEvent;
244
+ modalId: string;
245
+ source: 'internal' | 'external';
246
+ }
247
+ ```
248
+
249
+ **`ModalCloseHandler`**
250
+
251
+ ```ts
252
+ export type ModalCloseHandler = (event: ModalyzeCloseRequestEvent) => boolean;
253
+ ```
254
+
255
+ **Usage:**
256
+
257
+ ```ts
258
+ // From outside modal
259
+ const { setModalCloseRequestHandler } = useModalyze();
260
+ setModalCloseRequestHandler(modalId, (event) => {
261
+ if (hasUnsavedChanges) {
262
+ return confirm('Discard changes?');
263
+ }
264
+ return true;
265
+ });
266
+
267
+ // From inside modal
268
+ const { setCloseRequestHandler } = useModalyzeModal();
269
+ setCloseRequestHandler((event) => {
270
+ if (hasUnsavedChanges) {
271
+ return confirm('Discard changes?');
272
+ }
273
+ return true;
274
+ });
275
+ ```
276
+
277
+ ## License
278
+
279
+ MIT License
280
+
281
+ Copyright (c) 2026 Kaundur
282
+
283
+ 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:
284
+
285
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
286
+
287
+ 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.
@@ -0,0 +1,176 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentType } from 'react';
3
+
4
+ /**
5
+ * Reasons why a modal close was requested.
6
+ * Used in ModalyzeCloseRequestEvent to help handlers decide whether to allow closing.
7
+ */
8
+ declare const modalyzeCloseReason: {
9
+ readonly escape: "escape";
10
+ readonly outside: "outside";
11
+ readonly manual: "manual";
12
+ };
13
+ type ModalyzeCloseRequestEventReason = (typeof modalyzeCloseReason)[keyof typeof modalyzeCloseReason];
14
+ /**
15
+ * Event object passed to close request handlers when a modal is about to close.
16
+ * Contains information about what triggered the close request, allowing handlers
17
+ * to make informed decisions about whether to allow or prevent closing.
18
+ *
19
+ * @property reason - Why the close was requested (escape key, click outside, etc.)
20
+ * @property nativeEvent - The browser event that triggered the close, if applicable
21
+ * @property modalId - ID of the modal being closed
22
+ * @property source - Whether close was initiated from inside the modal ('internal')
23
+ * or outside ('external', e.g., via closeModal() or click outside)
24
+ */
25
+ interface ModalyzeCloseRequestEvent {
26
+ reason: ModalyzeCloseRequestEventReason;
27
+ nativeEvent?: MouseEvent | TouchEvent | KeyboardEvent;
28
+ modalId: string;
29
+ source: 'internal' | 'external';
30
+ }
31
+ /**
32
+ * Handler function called when a modal is about to close.
33
+ * Return `false` to prevent the modal from closing, or `true` to allow it.
34
+ *
35
+ * Useful for confirming unsaved changes, validating form state, or implementing
36
+ * custom close logic based on how the close was triggered.
37
+ *
38
+ * @param event - Information about the close request (reason, source, native event)
39
+ * @returns `true` to allow closing, `false` to prevent it
40
+ */
41
+ type ModalCloseHandler = (event: ModalyzeCloseRequestEvent) => boolean;
42
+
43
+ /**
44
+ * Context API available to components rendered inside modals.
45
+ * Provides modal-specific utilities like close, resize, and focus state.
46
+ *
47
+ * @internal This context is created automatically by Modalyze. Components should
48
+ * use the `useModalyzeModal()` hook instead of consuming this context directly.
49
+ */
50
+ type ModalyzeModalContextType = {
51
+ /** Closes this modal */
52
+ close: () => void;
53
+ /** Unique identifier for this modal */
54
+ modalId: string;
55
+ /** Whether this modal is currently focused */
56
+ isFocusedModal: boolean;
57
+ /** Whether this modal is the top of the modal stack*/
58
+ isTopModal: boolean;
59
+ /** Sets a close request handler for this modal */
60
+ setCloseRequestHandler: (handler: ModalCloseHandler | null) => void;
61
+ /** Resizes this modal to the specified dimensions, return the updated size */
62
+ setSize: (width: number, height: number) => {
63
+ width: number;
64
+ height: number;
65
+ } | null;
66
+ /** Moves this modal to the specified coordinates, return the updated position */
67
+ setPosition: (x: number, y: number) => {
68
+ x: number;
69
+ y: number;
70
+ } | null;
71
+ };
72
+ /**
73
+ * Hook for accessing modal-specific APIs from within a modal component.
74
+ * Provides utilities to control the modal that contains this component.
75
+ *
76
+ * Note: This hook can only be used inside components rendered within a modal.
77
+ * Use `useModalyze()` for managing modals from outside.
78
+ *
79
+ * @returns Modal control API for the current modal
80
+ * @returns close - Closes this modal
81
+ * @returns modalId - Unique identifier for this modal
82
+ * @returns isFocusedModal - Whether this modal is currently focused
83
+ * @returns setCloseRequestHandler - Sets a handler to intercept close requests
84
+ * @returns setSize - Resizes this modal
85
+ * @returns setPosition - Moves this modal
86
+ *
87
+ * @throws Error if used outside a modal component
88
+ */
89
+ declare const useModalyzeModal: () => ModalyzeModalContextType;
90
+
91
+ declare const Modalyze: ({ children }: {
92
+ children?: ReactNode;
93
+ }) => react_jsx_runtime.JSX.Element | null;
94
+
95
+ type ModalComponent<P = Record<string, unknown>> = ComponentType<P>;
96
+ /**
97
+ * Shared configuration options for modal behavior.
98
+ * Controls dismissibility, size constraints, and positioning.
99
+ */
100
+ type ModalBehaviorConfig = {
101
+ /** Close modal when escape is pressed and the modal is focused (default: true) */
102
+ closeOnEscape?: boolean;
103
+ /** Close modal when clicking outside the modal (default: false) */
104
+ closeOnOutsideClick?: boolean;
105
+ /** Minimum size constraints for the modal when resizing */
106
+ minSize?: {
107
+ width: number;
108
+ height: number;
109
+ };
110
+ /** Initial position of the modal in pixels from top-left of viewport */
111
+ position?: {
112
+ x: number;
113
+ y: number;
114
+ };
115
+ };
116
+ /**
117
+ * Configuration options for creating a modal.
118
+ * Extends behavior config with display options like title and initial size.
119
+ */
120
+ type ModalConfig = ModalBehaviorConfig & {
121
+ /** Title displayed in the modal's title bar */
122
+ title?: string;
123
+ /** Initial size of the modal in pixels. Modal is resizable unless disabled. */
124
+ size?: {
125
+ width: number;
126
+ height: number;
127
+ };
128
+ };
129
+ /**
130
+ * Complete configuration for creating a modal imperatively.
131
+ * Extends ModalConfig with optional custom props to pass to the modal component.
132
+ */
133
+ type ModalCreationOptions<P = Record<string, unknown>> = ModalConfig & {
134
+ props?: P;
135
+ };
136
+ /**
137
+ * Sets a close request handler for a modal. The handler is called whenever the modal
138
+ * is about to close and can prevent closing by returning false.
139
+ *
140
+ * @param modalId - The ID of the modal
141
+ * @param handler - Function called when close is requested. Return false to prevent
142
+ * closing, true to allow. Pass null to remove an existing handler.
143
+ *
144
+ */
145
+ declare function setModalCloseRequestHandler(modalId: string, handler: ModalCloseHandler | null): void;
146
+
147
+ /**
148
+ * Public hook for managing modals from outside modal components.
149
+ * Provides access to the modal stack state and imperative control methods.
150
+ *
151
+ * @returns Modal management API
152
+ * @returns modalIds - Array of all open modal IDs in stack order (bottom to top)
153
+ * @returns modalCount - Total number of open modals
154
+ * @returns focusedModalId - ID of currently focused modal, or null if none focused
155
+ * @returns frontModalId - ID of topmost modal in z-order, or null if no modals open
156
+ * @returns createModal - Function to create a new modal imperatively
157
+ * @returns closeModal - Function to close a specific modal by ID
158
+ * @returns closeAllModals - Function to close all open modals
159
+ * @returns setModalCloseRequestHandler - Function to set a close request handler for a modal
160
+ * @returns setFocusedModal - Function to programmatically focus a modal
161
+ * @returns bringModalToFront - Function to bring modal to the front of the modal stack
162
+ */
163
+ declare function useModalyze(): {
164
+ modalIds: string[];
165
+ modalCount: number;
166
+ focusedModalId: string | null;
167
+ frontModalId: string | null;
168
+ createModal: <P extends object = Record<string, unknown>>(component: ModalComponent<P>, options?: ModalCreationOptions<P>) => string;
169
+ closeModal: (modalId: string) => void;
170
+ closeAllModals: () => void;
171
+ setModalCloseRequestHandler: typeof setModalCloseRequestHandler;
172
+ setFocusedModal: (modalId?: string) => void;
173
+ bringModalToFront: (modalId: string) => void;
174
+ };
175
+
176
+ export { type ModalConfig, type ModalCreationOptions, Modalyze, type ModalyzeCloseRequestEvent, type ModalyzeCloseRequestEventReason, useModalyze, useModalyzeModal };
@@ -0,0 +1,176 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentType } from 'react';
3
+
4
+ /**
5
+ * Reasons why a modal close was requested.
6
+ * Used in ModalyzeCloseRequestEvent to help handlers decide whether to allow closing.
7
+ */
8
+ declare const modalyzeCloseReason: {
9
+ readonly escape: "escape";
10
+ readonly outside: "outside";
11
+ readonly manual: "manual";
12
+ };
13
+ type ModalyzeCloseRequestEventReason = (typeof modalyzeCloseReason)[keyof typeof modalyzeCloseReason];
14
+ /**
15
+ * Event object passed to close request handlers when a modal is about to close.
16
+ * Contains information about what triggered the close request, allowing handlers
17
+ * to make informed decisions about whether to allow or prevent closing.
18
+ *
19
+ * @property reason - Why the close was requested (escape key, click outside, etc.)
20
+ * @property nativeEvent - The browser event that triggered the close, if applicable
21
+ * @property modalId - ID of the modal being closed
22
+ * @property source - Whether close was initiated from inside the modal ('internal')
23
+ * or outside ('external', e.g., via closeModal() or click outside)
24
+ */
25
+ interface ModalyzeCloseRequestEvent {
26
+ reason: ModalyzeCloseRequestEventReason;
27
+ nativeEvent?: MouseEvent | TouchEvent | KeyboardEvent;
28
+ modalId: string;
29
+ source: 'internal' | 'external';
30
+ }
31
+ /**
32
+ * Handler function called when a modal is about to close.
33
+ * Return `false` to prevent the modal from closing, or `true` to allow it.
34
+ *
35
+ * Useful for confirming unsaved changes, validating form state, or implementing
36
+ * custom close logic based on how the close was triggered.
37
+ *
38
+ * @param event - Information about the close request (reason, source, native event)
39
+ * @returns `true` to allow closing, `false` to prevent it
40
+ */
41
+ type ModalCloseHandler = (event: ModalyzeCloseRequestEvent) => boolean;
42
+
43
+ /**
44
+ * Context API available to components rendered inside modals.
45
+ * Provides modal-specific utilities like close, resize, and focus state.
46
+ *
47
+ * @internal This context is created automatically by Modalyze. Components should
48
+ * use the `useModalyzeModal()` hook instead of consuming this context directly.
49
+ */
50
+ type ModalyzeModalContextType = {
51
+ /** Closes this modal */
52
+ close: () => void;
53
+ /** Unique identifier for this modal */
54
+ modalId: string;
55
+ /** Whether this modal is currently focused */
56
+ isFocusedModal: boolean;
57
+ /** Whether this modal is the top of the modal stack*/
58
+ isTopModal: boolean;
59
+ /** Sets a close request handler for this modal */
60
+ setCloseRequestHandler: (handler: ModalCloseHandler | null) => void;
61
+ /** Resizes this modal to the specified dimensions, return the updated size */
62
+ setSize: (width: number, height: number) => {
63
+ width: number;
64
+ height: number;
65
+ } | null;
66
+ /** Moves this modal to the specified coordinates, return the updated position */
67
+ setPosition: (x: number, y: number) => {
68
+ x: number;
69
+ y: number;
70
+ } | null;
71
+ };
72
+ /**
73
+ * Hook for accessing modal-specific APIs from within a modal component.
74
+ * Provides utilities to control the modal that contains this component.
75
+ *
76
+ * Note: This hook can only be used inside components rendered within a modal.
77
+ * Use `useModalyze()` for managing modals from outside.
78
+ *
79
+ * @returns Modal control API for the current modal
80
+ * @returns close - Closes this modal
81
+ * @returns modalId - Unique identifier for this modal
82
+ * @returns isFocusedModal - Whether this modal is currently focused
83
+ * @returns setCloseRequestHandler - Sets a handler to intercept close requests
84
+ * @returns setSize - Resizes this modal
85
+ * @returns setPosition - Moves this modal
86
+ *
87
+ * @throws Error if used outside a modal component
88
+ */
89
+ declare const useModalyzeModal: () => ModalyzeModalContextType;
90
+
91
+ declare const Modalyze: ({ children }: {
92
+ children?: ReactNode;
93
+ }) => react_jsx_runtime.JSX.Element | null;
94
+
95
+ type ModalComponent<P = Record<string, unknown>> = ComponentType<P>;
96
+ /**
97
+ * Shared configuration options for modal behavior.
98
+ * Controls dismissibility, size constraints, and positioning.
99
+ */
100
+ type ModalBehaviorConfig = {
101
+ /** Close modal when escape is pressed and the modal is focused (default: true) */
102
+ closeOnEscape?: boolean;
103
+ /** Close modal when clicking outside the modal (default: false) */
104
+ closeOnOutsideClick?: boolean;
105
+ /** Minimum size constraints for the modal when resizing */
106
+ minSize?: {
107
+ width: number;
108
+ height: number;
109
+ };
110
+ /** Initial position of the modal in pixels from top-left of viewport */
111
+ position?: {
112
+ x: number;
113
+ y: number;
114
+ };
115
+ };
116
+ /**
117
+ * Configuration options for creating a modal.
118
+ * Extends behavior config with display options like title and initial size.
119
+ */
120
+ type ModalConfig = ModalBehaviorConfig & {
121
+ /** Title displayed in the modal's title bar */
122
+ title?: string;
123
+ /** Initial size of the modal in pixels. Modal is resizable unless disabled. */
124
+ size?: {
125
+ width: number;
126
+ height: number;
127
+ };
128
+ };
129
+ /**
130
+ * Complete configuration for creating a modal imperatively.
131
+ * Extends ModalConfig with optional custom props to pass to the modal component.
132
+ */
133
+ type ModalCreationOptions<P = Record<string, unknown>> = ModalConfig & {
134
+ props?: P;
135
+ };
136
+ /**
137
+ * Sets a close request handler for a modal. The handler is called whenever the modal
138
+ * is about to close and can prevent closing by returning false.
139
+ *
140
+ * @param modalId - The ID of the modal
141
+ * @param handler - Function called when close is requested. Return false to prevent
142
+ * closing, true to allow. Pass null to remove an existing handler.
143
+ *
144
+ */
145
+ declare function setModalCloseRequestHandler(modalId: string, handler: ModalCloseHandler | null): void;
146
+
147
+ /**
148
+ * Public hook for managing modals from outside modal components.
149
+ * Provides access to the modal stack state and imperative control methods.
150
+ *
151
+ * @returns Modal management API
152
+ * @returns modalIds - Array of all open modal IDs in stack order (bottom to top)
153
+ * @returns modalCount - Total number of open modals
154
+ * @returns focusedModalId - ID of currently focused modal, or null if none focused
155
+ * @returns frontModalId - ID of topmost modal in z-order, or null if no modals open
156
+ * @returns createModal - Function to create a new modal imperatively
157
+ * @returns closeModal - Function to close a specific modal by ID
158
+ * @returns closeAllModals - Function to close all open modals
159
+ * @returns setModalCloseRequestHandler - Function to set a close request handler for a modal
160
+ * @returns setFocusedModal - Function to programmatically focus a modal
161
+ * @returns bringModalToFront - Function to bring modal to the front of the modal stack
162
+ */
163
+ declare function useModalyze(): {
164
+ modalIds: string[];
165
+ modalCount: number;
166
+ focusedModalId: string | null;
167
+ frontModalId: string | null;
168
+ createModal: <P extends object = Record<string, unknown>>(component: ModalComponent<P>, options?: ModalCreationOptions<P>) => string;
169
+ closeModal: (modalId: string) => void;
170
+ closeAllModals: () => void;
171
+ setModalCloseRequestHandler: typeof setModalCloseRequestHandler;
172
+ setFocusedModal: (modalId?: string) => void;
173
+ bringModalToFront: (modalId: string) => void;
174
+ };
175
+
176
+ export { type ModalConfig, type ModalCreationOptions, Modalyze, type ModalyzeCloseRequestEvent, type ModalyzeCloseRequestEventReason, useModalyze, useModalyzeModal };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";var ie=Object.defineProperty;var Ke=Object.getOwnPropertyDescriptor;var $e=Object.getOwnPropertyNames;var _e=Object.prototype.hasOwnProperty;var Ae=(e,o)=>{for(var t in o)ie(e,t,{get:o[t],enumerable:!0})},Ve=(e,o,t,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let a of $e(o))!_e.call(e,a)&&a!==t&&ie(e,a,{get:()=>o[a],enumerable:!(n=Ke(o,a))||n.enumerable});return e};var Ge=e=>Ve(ie({},"__esModule",{value:!0}),e);var oo={};Ae(oo,{Modalyze:()=>Ye,useModalyze:()=>Y,useModalyzeModal:()=>S});module.exports=Ge(oo);function ce(e,{insertAt:o}={}){if(!e||typeof document>"u")return;let t=document.head||document.getElementsByTagName("head")[0],n=document.createElement("style");n.type="text/css",o==="top"&&t.firstChild?t.insertBefore(n,t.firstChild):t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}ce(`:root{--modalyze-font-family: sans-serif;--modalyze-modal-box-shadow: 0 10px 30px rgba(0, 0, 0, .2);--modalyze-modal-box-shadow-focused: 0 0 8px 2px dimgrey;--modalyze-modal-content-background-color: #fff;--modalyze-modal-header-background-color: #f5f5f5;--modalyze-modal-header-background-color-focused: #d5d5d5;--modalyze-modal-header-border-bottom-color: #ddd;--modalyze-modal-header-close-button-color: #666;--modalyze-modal-header-close-button-hover-color: #000}.modalyze-modal{position:fixed;top:0;left:0;width:400px;height:300px;display:flex;flex-direction:column;font-family:var(--modalyze-font-family);z-index:1000}.modalyze-modal-focused:after{content:"";position:absolute;inset:0;pointer-events:none;border-radius:3px;box-shadow:var(--modalyze-modal-box-shadow-focused);opacity:.9;transition:opacity .2s ease}.modalyze-modal-content{position:absolute;box-sizing:border-box;width:100%;height:100%;display:flex;overflow:hidden;flex-direction:column;background-color:var(--modalyze-modal-content-background-color);box-shadow:var(--modalyze-modal-box-shadow);border-radius:3px}.modalyze-modal-header{display:flex;align-items:center;justify-content:space-between;background-color:var(--modalyze-modal-header-background-color);padding:.75rem 1rem;cursor:grab;border-bottom:1px solid var(--modalyze-modal-header-border-bottom-color);transition:background-color .2s ease}.modalyze-modal-focused .modalyze-modal-header{background-color:var(--modalyze-modal-header-background-color-focused)}.modalyze-modal-header-dragging{cursor:grabbing}.modalyze-modal-header-title{font-weight:700;font-size:1rem;flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modalyze-modal-header-close-button{background:none;border:none;font-size:1.25rem;cursor:pointer;padding:0;margin-left:1rem;line-height:1;color:var(--modalyze-modal-header-close-button-color);transition:color .2s ease}.modalyze-modal-header-close-button:hover,.modalyze-modal-header-close-button:focus-visible{color:var(--modalyze-modal-header-close-button-hover-color)}.modalyze-modal-body{padding:1rem;overflow-y:auto;flex-grow:1}.modalyze-resizer-left{position:absolute;top:0;left:-4px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-right{position:absolute;top:0;right:-2px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-top{position:absolute;top:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-bottom{position:absolute;bottom:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-top-left{position:absolute;top:-2px;left:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}.modalyze-resizer-top-right{position:absolute;top:-2px;right:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-left{position:absolute;bottom:-2px;left:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-right{position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}
2
+ `);var J=require("react"),ue=(0,J.createContext)(null),S=()=>{let e=(0,J.useContext)(ue);if(!e)throw new Error("useModalyzeModal must be used within a Modalyze modal");return e};var M=require("react"),se=require("react-dom");var Xe=require("react");var ge=require("react");var le=require("react");var O=require("react");var j=require("react");function xe(){let e=(0,j.useRef)(!1),[o,t]=(0,j.useState)(!1),n=(0,j.useCallback)(a=>{e.current=a,t(a)},[]);return{isDragging:o,isDraggingRef:e,setDragging:n}}var be=e=>{let{isDragging:o,isDraggingRef:t,setDragging:n}=xe(),{setPosition:a}=S(),d=(0,O.useRef)({x:0,y:0}),x=(0,O.useCallback)(u=>{if(u.preventDefault(),n(!0),document.body.style.userSelect="none",u.currentTarget.setPointerCapture(u.pointerId),e.current){let s=e.current.getBoundingClientRect();d.current={x:u.clientX-s.left,y:u.clientY-s.top}}},[e,n]),l=(0,O.useCallback)(()=>{n(!1),document.body.style.userSelect=""},[n]),f=(0,O.useCallback)(u=>{t.current&&a(u.clientX-d.current.x,u.clientY-d.current.y)},[a,t]);return(0,O.useEffect)(()=>(window.addEventListener("pointermove",f,{passive:!0}),window.addEventListener("pointerup",l),()=>{window.removeEventListener("pointermove",f),window.removeEventListener("pointerup",l)}),[f,l]),{isDragging:o,onPointerDown:x}};var X=require("react");var ze=require("react"),U=(0,ze.createContext)(null);function Y(){let o=(0,X.useContext)(U)?.instanceId,{modalStack:t,focusedModalId:n}=Q(),a=(0,X.useCallback)((d,x)=>ve(d,x,o),[o]);return(0,X.useMemo)(()=>({modalIds:t.map(d=>d.modalId),modalCount:t.length,focusedModalId:n,frontModalId:t.at(-1)?.modalId??null,createModal:a,closeModal:Ee,closeAllModals:we,setModalCloseRequestHandler:Ce,setFocusedModal:Re,bringModalToFront:me}),[n,t,a])}var G=require("react");function pe(...e){return e.filter(Boolean).join(" ")}var T=require("react");var ee=require("react"),Ie=()=>({left:0,right:window.innerWidth,top:0,bottom:window.innerHeight}),oe=()=>{let[e,o]=(0,ee.useState)(Ie);return(0,ee.useEffect)(()=>{let t=()=>o(Ie());return window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[]),e};var te=require("react"),Pe={escape:"escape",outside:"outside",manual:"manual"},fe=(0,te.createContext)(null),He=()=>{let e=(0,te.useContext)(fe);if(!e)throw new Error("[Modalyze Internal Error] useModalyzeModalInternal called outside modal context. This is likely a bug in Modalyze.");return e};var m=require("react");var ke=require("react");function Te(e){(0,ke.useEffect)(()=>{function o(t){t.key==="Escape"&&!t.repeat&&(t.stopPropagation(),e(t))}return window.addEventListener("keydown",o),()=>{window.removeEventListener("keydown",o)}},[e])}var Be=require("react"),Le=(e,o)=>{(0,Be.useEffect)(()=>{function t(n){if(!e.current)return;let a=n.target;e.current.contains(a)||o(n)}return document.addEventListener("mousedown",t),document.addEventListener("touchstart",t),()=>{document.removeEventListener("mousedown",t),document.removeEventListener("touchstart",t)}},[o,e])};var De=require("react");var ne=e=>(0,De.useSyncExternalStore)(V,Se)===e;var re=require("react/jsx-runtime"),N=2,Ze={width:300,height:200},Oe=({children:e,modalId:o,closeOnEscape:t=!0,closeOnOutsideClick:n=!1,minSize:a=Ze,position:d})=>{let x=(0,m.useRef)(null),l=(0,m.useRef)(null),f=(0,m.useRef)(!1),u=(0,m.useRef)({x:0,y:0}),s=oe(),{setModalCloseRequestHandler:R,frontModalId:H,setFocusedModal:w}=Y(),{removeModal:r,getModalCloseHandler:i}=ae(),b=ne(o),D=(0,m.useMemo)(()=>o===H,[o,H]),h=(0,m.useCallback)(c=>{R(o,c)},[o,R]),y=(0,m.useCallback)(c=>{let p=i(o);p?p(c)&&r(o):r(o)},[i,o,r]),q=(0,m.useCallback)(c=>{if(!t||!b)return;y({reason:"escape",nativeEvent:c,modalId:o,source:"internal"})},[y,t,b,o]);Te(q);let K=(0,m.useCallback)(c=>{if(w(),!n)return;let p={reason:Pe.outside,nativeEvent:c,modalId:o,source:"internal"};y(p)},[y,n,o,w]);Le(x,K);let $=(0,m.useCallback)(()=>{y({reason:"manual",modalId:o,source:"external"})},[y,o]),_=(0,m.useCallback)((c,p)=>{let v=l.current;if(!v)return null;let g=Math.max(c,a.width),C=Math.max(p,a.height);return v.style.width=`${g}px`,v.style.height=`${C}px`,{width:g,height:C}},[a.height,a.width]),W=(0,m.useCallback)(()=>{let c=s.left,p=s.top,v=s.right-(l.current?.offsetWidth??0)-N,g=s.bottom-(l.current?.offsetHeight??0)-N;return{minX:c,minY:p,maxX:v,maxY:g}},[s]),I=(0,m.useCallback)((c,p)=>{let v=l.current;if(!v)return null;let g=W(),C=Math.max(g.minX,Math.min(c,g.maxX)),k=Math.max(g.minY,Math.min(p,g.maxY));return u.current={x:C,y:k},v.style.transform=`translate(${C}px, ${k}px)`,{x:C,y:k}},[W]);return(0,m.useLayoutEffect)(()=>{if(l.current&&!f.current){if(d)I(d.x,d.y);else{let c=s.right-s.left,p=s.bottom-s.top,v=s.left+c/2-l.current.offsetWidth/2,g=s.top+p/2-l.current.offsetHeight/2;I(v,g)}f.current=!0}},[I,s,l,d]),(0,m.useEffect)(()=>{let{x:c,y:p}=u.current;I(c,p)},[I,s]),(0,re.jsx)(fe.Provider,{value:{containerRef:x,modalRef:l,minSize:a},children:(0,re.jsx)(ue.Provider,{value:{close:$,modalId:o,isFocusedModal:b,isTopModal:D,setCloseRequestHandler:h,setSize:_,setPosition:I},children:(0,re.jsx)("div",{ref:x,children:e})})})};var Fe=require("react/jsx-runtime"),Je={left:"modalyze-resizer-left",right:"modalyze-resizer-right",top:"modalyze-resizer-top",bottom:"modalyze-resizer-bottom","top-left":"modalyze-resizer-top-left","top-right":"modalyze-resizer-top-right","bottom-left":"modalyze-resizer-bottom-left","bottom-right":"modalyze-resizer-bottom-right"},B=({containerRef:e,left:o,right:t,top:n,bottom:a})=>{let{setSize:d,setPosition:x}=S(),l=oe(),f=(0,T.useRef)(!1),u=(0,T.useCallback)(r=>{r.preventDefault(),r.stopPropagation(),f.current=!0,document.body.style.userSelect="none"},[]),s=(0,T.useCallback)(()=>{f.current=!1,document.body.style.userSelect=""},[]),R=(0,T.useCallback)(r=>{let i=e.current;if(!f.current||!i)return;let{clientX:b,clientY:D}=r,h=i.getBoundingClientRect(),y=Math.round(Math.min(Math.max(b,l.left+N),l.right-N)),q=Math.round(Math.min(Math.max(D,l.top+N),l.bottom-N)),K=Math.round(h.left),$=Math.round(h.right),_=Math.round(h.top),W=Math.round(h.bottom),I=Math.round(h.width),c=Math.round(h.height),p=K,v=_;if(t){let C=l.right-K;I=Math.min(y-K,C)}else if(o){let C=y,k=l.left,A=Math.max(C,k),de=$-k;I=Math.min($-A,de),p=A}if(a){let C=l.bottom-_;c=Math.min(q-_,C)}else if(n){let C=q,k=l.top,A=Math.max(C,k),de=W-k;c=Math.min(W-A,de),v=A}let g=d(I,c);g&&(o&&g.width>I&&(p=$-g.width),n&&g.height>c&&(v=W-g.height),x(p,v))},[e,t,o,a,n,d,x,l]);(0,T.useEffect)(()=>{let r=e.current;if(!r)return;let i=r.getBoundingClientRect(),b=l.right-l.left,D=l.bottom-l.top,h=i.width,y=i.height;h>b&&(h=b),y>D&&(y=D),(h!==i.width||y!==i.height)&&d(h,y)},[l,e,d]),(0,T.useEffect)(()=>(window.addEventListener("pointermove",R,{passive:!0}),window.addEventListener("pointerup",s),()=>{window.removeEventListener("pointermove",R),window.removeEventListener("pointerup",s)}),[R,s]);let H=[n&&"top",a&&"bottom",o&&"left",t&&"right"].filter(Boolean).join("-"),w=Je[H];return(0,Fe.jsx)("div",{className:w,onPointerDown:u})};var E=require("react/jsx-runtime"),qe=({containerRef:e})=>(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(B,{containerRef:e,left:!0}),(0,E.jsx)(B,{containerRef:e,right:!0}),(0,E.jsx)(B,{containerRef:e,top:!0}),(0,E.jsx)(B,{containerRef:e,bottom:!0}),(0,E.jsx)(B,{containerRef:e,top:!0,left:!0}),(0,E.jsx)(B,{containerRef:e,top:!0,right:!0}),(0,E.jsx)(B,{containerRef:e,bottom:!0,left:!0}),(0,E.jsx)(B,{containerRef:e,bottom:!0,right:!0})]});var L=require("react/jsx-runtime"),Qe={width:500,height:400},Ne=({children:e,title:o,size:t=Qe})=>{let{modalRef:n,minSize:a}=He(),{modalId:d,close:x}=S(),{setFocusedModal:l}=Y(),{isDragging:f,onPointerDown:u}=be(n),s=ne(d),R=Math.max(t.width,a.width),H=Math.max(t.height,a.height),w=(0,G.useCallback)((r,i)=>{r.stopPropagation(),s||r.preventDefault(),l(d),i&&u(r),n.current?.focus()},[s,d,n,u,l]);return(0,G.useEffect)(()=>{n.current?.focus()},[n]),(0,G.useEffect)(()=>{if(!s)return;let r=n.current;if(!r)return;let i=b=>{if(b.key!=="Tab")return;let D=r.querySelectorAll('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'),h=Array.from(D);if(h.length===0){b.preventDefault();return}let y=h[0],q=h[h.length-1];b.shiftKey&&document.activeElement===y?(b.preventDefault(),q.focus()):!b.shiftKey&&document.activeElement===q&&(b.preventDefault(),y.focus())};return r.addEventListener("keydown",i),()=>r.removeEventListener("keydown",i)},[s,n]),(0,L.jsxs)("div",{className:pe("modalyze-modal",s&&"modalyze-modal-focused"),ref:n,tabIndex:-1,role:"dialog","aria-modal":"false","aria-labelledby":o!=null?`modal-title-${d}`:void 0,"aria-describedby":`modal-content-${d}`,onPointerDown:r=>w(r,!0),style:{width:`${R}px`,height:`${H}px`},children:[(0,L.jsx)(qe,{containerRef:n}),(0,L.jsxs)("div",{className:"modalyze-modal-content",children:[(0,L.jsxs)("div",{className:pe("modalyze-modal-header",f&&"modalyze-modal-header-dragging"),onPointerDown:r=>w(r,!0),children:[(0,L.jsx)("div",{className:"modalyze-modal-header-title",id:o!=null?`modal-title-${d}`:void 0,children:o}),(0,L.jsx)("button",{type:"button",onClick:()=>x(),onPointerDown:r=>r.stopPropagation(),"aria-label":"Close modal",className:"modalyze-modal-header-close-button",children:"\xD7"})]}),(0,L.jsx)("div",{className:"modalyze-modal-body",id:`modal-content-${d}`,onPointerDown:r=>w(r,!1),children:e})]})]})};var z=[],he=new Set,P=null,Z=()=>{he.forEach(e=>e())};function ve(e,o,t){let n=crypto.randomUUID(),{title:a,size:d,props:x,...l}=o??{},f=x??{};if(!document.getElementById("modalyze-root"))return console.warn("Modalyze: createModal called before <Modalyze> mounted. Ensure <Modalyze> is mounted before creating modals."),"";let s=(0,le.createElement)(Oe,{modalId:n,...l,children:(0,le.createElement)(Ne,{title:a,size:d},(0,le.createElement)(e,f))});return z=[...z,{modalId:n,element:s,closeHandler:null,containerId:t}],P=n,Z(),n}var ye=e=>{z=z.filter(o=>o.modalId!==e),P===e&&(P=null),Z()},We=e=>{let o=z.find(t=>t.modalId===e);return o?o.closeHandler:null};function Ce(e,o){let t=z.find(n=>n.modalId===e);t&&(t.closeHandler=o)}var Ee=e=>{let o=z.find(n=>n.modalId===e);if(!o)return;let t={reason:"manual",source:"external",modalId:e};o.closeHandler&&!o.closeHandler(t)||ye(e)},we=()=>{let e=[];for(let t of[...z]){let n={reason:"manual",source:"external",modalId:t.modalId};t.closeHandler&&!t.closeHandler(n)||e.push(t.modalId)}if(e.length===0)return;let o=new Set(e);z=z.filter(t=>!o.has(t.modalId)),P!=null&&o.has(P)&&(P=null),Z()},me=e=>{let o=z.findIndex(n=>n.modalId===e);if(o===-1)return;let t=z[o];z=[...z.filter((n,a)=>a!==o),t],Z()},Re=e=>{e==null?P=null:(me(e),P=e),Z()},Se=()=>P,V=e=>(he.add(e),()=>he.delete(e)),je=()=>z,Ue=()=>P;var Q=()=>{let e=(0,ge.useSyncExternalStore)(V,je),o=(0,ge.useSyncExternalStore)(V,Ue);return{modalStack:e,focusedModalId:o}};var ae=()=>{let{modalStack:e}=Q();return(0,Xe.useMemo)(()=>({modalStack:e,removeModal:ye,getModalCloseHandler:We}),[e])};var F=require("react/jsx-runtime");function eo(){let[,e]=(0,M.useReducer)(o=>o+1,0);return e}var Me=!1,Ye=({children:e})=>{let o=(0,M.useContext)(U),t=o!==null;(0,M.useEffect)(()=>{if(!t)return Me&&console.warn("Multiple root <Modalyze> components detected. Only mount one <Modalyze> at your app root."),Me=!0,()=>{Me=!1}},[t]);let n=o===null,a=(0,M.useMemo)(()=>crypto.randomUUID(),[]),{modalStack:d}=ae(),[x,l]=(0,M.useState)(null),f=(0,M.useRef)(new Map),u=eo();(0,M.useEffect)(()=>{if(!n)return;let r=document.createElement("div");return r.id="modalyze-root",document.body.appendChild(r),l(r),()=>{document.body.removeChild(r)}},[n]),(0,M.useLayoutEffect)(()=>{n&&u()},[n,d.length,u]);let s=n?x:o.rootContainer,R=d.filter(r=>n&&r.containerId==null||r.containerId===a),H=(0,M.useCallback)(r=>f.current.get(r)??null,[]);if(s==null)return null;let w=o?.getModalContainer??H;return n?(0,F.jsxs)(U.Provider,{value:{instanceId:a,parent:o,rootContainer:s,getModalContainer:H},children:[(0,se.createPortal)((0,F.jsx)(F.Fragment,{children:d.map(r=>(0,F.jsx)("div",{ref:i=>{i?f.current.set(r.modalId,i):f.current.delete(r.modalId)},"data-modal-id":r.modalId},r.modalId))}),s),R.map(r=>{let i=w(r.modalId);return i&&(0,se.createPortal)(r.element,i,r.modalId)}),e]}):(0,F.jsxs)(U.Provider,{value:{instanceId:a,parent:o,rootContainer:s,getModalContainer:w},children:[R.map(r=>{let i=w(r.modalId);return i&&(0,se.createPortal)(r.element,i,r.modalId)}),e]})};0&&(module.exports={Modalyze,useModalyze,useModalyzeModal});
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","#style-inject:#style-inject","../src/styles.css","../src/contexts/ModalyzeModalContext.tsx","../src/components/Modalyze.tsx","../src/hooks/useModalyzeInternal.ts","../src/hooks/useModalyzeBase.ts","../src/modalStore.ts","../src/hooks/useDraggable.tsx","../src/hooks/useDraggableState.tsx","../src/hooks/useModalyze.ts","../src/contexts/ModalyzeContext.ts","../src/components/BaseModal.tsx","../src/Utils.ts","../src/components/ResizerBar.tsx","../src/hooks/useContainerBounds.tsx","../src/contexts/ModalyzeModalInternalContext.tsx","../src/components/ModalContextWrapper.tsx","../src/hooks/useEscapeKey.ts","../src/hooks/useClickOutsideElement.tsx","../src/hooks/useIsModalFocused.tsx","../src/components/Resizer.tsx"],"sourcesContent":["import './styles.css';\n\nexport { useModalyzeModal } from './contexts/ModalyzeModalContext';\nexport { Modalyze } from './components/Modalyze';\nexport { useModalyze } from './hooks/useModalyze';\nexport type { ModalConfig, ModalCreationOptions } from './modalStore';\nexport type {\n ModalyzeCloseRequestEvent,\n ModalyzeCloseRequestEventReason,\n} from './contexts/ModalyzeModalInternalContext';\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\":root{--modalyze-font-family: sans-serif;--modalyze-modal-box-shadow: 0 10px 30px rgba(0, 0, 0, .2);--modalyze-modal-box-shadow-focused: 0 0 8px 2px dimgrey;--modalyze-modal-content-background-color: #fff;--modalyze-modal-header-background-color: #f5f5f5;--modalyze-modal-header-background-color-focused: #d5d5d5;--modalyze-modal-header-border-bottom-color: #ddd;--modalyze-modal-header-close-button-color: #666;--modalyze-modal-header-close-button-hover-color: #000}.modalyze-modal{position:fixed;top:0;left:0;width:400px;height:300px;display:flex;flex-direction:column;font-family:var(--modalyze-font-family);z-index:1000}.modalyze-modal-focused:after{content:\\\"\\\";position:absolute;inset:0;pointer-events:none;border-radius:3px;box-shadow:var(--modalyze-modal-box-shadow-focused);opacity:.9;transition:opacity .2s ease}.modalyze-modal-content{position:absolute;box-sizing:border-box;width:100%;height:100%;display:flex;overflow:hidden;flex-direction:column;background-color:var(--modalyze-modal-content-background-color);box-shadow:var(--modalyze-modal-box-shadow);border-radius:3px}.modalyze-modal-header{display:flex;align-items:center;justify-content:space-between;background-color:var(--modalyze-modal-header-background-color);padding:.75rem 1rem;cursor:grab;border-bottom:1px solid var(--modalyze-modal-header-border-bottom-color);transition:background-color .2s ease}.modalyze-modal-focused .modalyze-modal-header{background-color:var(--modalyze-modal-header-background-color-focused)}.modalyze-modal-header-dragging{cursor:grabbing}.modalyze-modal-header-title{font-weight:700;font-size:1rem;flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modalyze-modal-header-close-button{background:none;border:none;font-size:1.25rem;cursor:pointer;padding:0;margin-left:1rem;line-height:1;color:var(--modalyze-modal-header-close-button-color);transition:color .2s ease}.modalyze-modal-header-close-button:hover,.modalyze-modal-header-close-button:focus-visible{color:var(--modalyze-modal-header-close-button-hover-color)}.modalyze-modal-body{padding:1rem;overflow-y:auto;flex-grow:1}.modalyze-resizer-left{position:absolute;top:0;left:-4px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-right{position:absolute;top:0;right:-2px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-top{position:absolute;top:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-bottom{position:absolute;bottom:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-top-left{position:absolute;top:-2px;left:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}.modalyze-resizer-top-right{position:absolute;top:-2px;right:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-left{position:absolute;bottom:-2px;left:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-right{position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}\\n\")","import { createContext, useContext } from 'react';\nimport { ModalCloseHandler } from './ModalyzeModalInternalContext';\n\n/**\n * Context API available to components rendered inside modals.\n * Provides modal-specific utilities like close, resize, and focus state.\n *\n * @internal This context is created automatically by Modalyze. Components should\n * use the `useModalyzeModal()` hook instead of consuming this context directly.\n */\ntype ModalyzeModalContextType = {\n /** Closes this modal */\n close: () => void;\n\n /** Unique identifier for this modal */\n modalId: string;\n\n /** Whether this modal is currently focused */\n isFocusedModal: boolean;\n\n /** Whether this modal is the top of the modal stack*/\n isTopModal: boolean;\n\n /** Sets a close request handler for this modal */\n setCloseRequestHandler: (handler: ModalCloseHandler | null) => void;\n\n /** Resizes this modal to the specified dimensions, return the updated size */\n setSize: (width: number, height: number) => { width: number; height: number } | null;\n\n /** Moves this modal to the specified coordinates, return the updated position */\n setPosition: (x: number, y: number) => { x: number; y: number } | null;\n};\n\nexport const ModalyzeModalContext = createContext<ModalyzeModalContextType | null>(null);\n\n/**\n * Hook for accessing modal-specific APIs from within a modal component.\n * Provides utilities to control the modal that contains this component.\n *\n * Note: This hook can only be used inside components rendered within a modal.\n * Use `useModalyze()` for managing modals from outside.\n *\n * @returns Modal control API for the current modal\n * @returns close - Closes this modal\n * @returns modalId - Unique identifier for this modal\n * @returns isFocusedModal - Whether this modal is currently focused\n * @returns setCloseRequestHandler - Sets a handler to intercept close requests\n * @returns setSize - Resizes this modal\n * @returns setPosition - Moves this modal\n *\n * @throws Error if used outside a modal component\n */\nexport const useModalyzeModal = () => {\n const ctx = useContext(ModalyzeModalContext);\n if (!ctx) throw new Error('useModalyzeModal must be used within a Modalyze modal');\n return ctx;\n};\n","import {\n ReactNode,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useModalyzeInternal } from '../hooks/useModalyzeInternal';\nimport { ModalyzeContext } from '../contexts/ModalyzeContext';\n\n/**\n * Returns a function that triggers a re-render without managing state.\n * Useful for synchronization patterns where you need to notify components\n * to re-check conditions (like DOM elements being ready).\n */\nfunction useForceUpdate() {\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n return forceUpdate;\n}\n\nlet hasRootInstance = false;\n\nexport const Modalyze = ({ children }: { children?: ReactNode }) => {\n const parent = useContext(ModalyzeContext);\n const hasParent = parent !== null;\n useEffect(() => {\n if (!hasParent) {\n if (hasRootInstance) {\n console.warn(\n 'Multiple root <Modalyze> components detected. ' +\n 'Only mount one <Modalyze> at your app root.'\n );\n }\n hasRootInstance = true;\n\n return () => {\n hasRootInstance = false;\n };\n }\n }, [hasParent]);\n\n const isRoot = parent === null;\n const instanceId = useMemo(() => crypto.randomUUID(), []);\n\n const { modalStack } = useModalyzeInternal();\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const modalContainerRefs = useRef<Map<string, HTMLDivElement>>(new Map());\n\n const forceUpdate = useForceUpdate();\n\n useEffect(() => {\n if (!isRoot) return;\n\n const div = document.createElement('div');\n div.id = 'modalyze-root';\n document.body.appendChild(div);\n setContainer(div);\n\n return () => {\n document.body.removeChild(div);\n };\n }, [isRoot]);\n\n /**\n * Synchronization pattern:\n * This useLayoutEffect triggers a re-render of the Modalyze component\n * (not modal contents or children) after containers are mounted in DOM.\n *\n * This allows nested instances to successfully find and portal to their containers.\n */\n useLayoutEffect(() => {\n if (!isRoot) return;\n forceUpdate();\n }, [isRoot, modalStack.length, forceUpdate]);\n\n const rootContainer = isRoot ? container : parent.rootContainer;\n\n const levelModals = modalStack.filter(\n (m) => (isRoot && m.containerId == null) || m.containerId === instanceId\n );\n\n const getModalContainer = useCallback((modalId: string) => {\n return modalContainerRefs.current.get(modalId) ?? null;\n }, []);\n\n if (rootContainer == null) return null;\n\n const parentGetContainer = parent?.getModalContainer ?? getModalContainer;\n\n if (isRoot) {\n return (\n <ModalyzeContext.Provider\n value={{ instanceId, parent, rootContainer, getModalContainer }}\n >\n {createPortal(\n <>\n {modalStack.map((m) => (\n <div\n key={m.modalId}\n ref={(el) => {\n if (el) {\n modalContainerRefs.current.set(m.modalId, el);\n } else {\n modalContainerRefs.current.delete(m.modalId);\n }\n }}\n data-modal-id={m.modalId}\n />\n ))}\n </>,\n rootContainer\n )}\n {levelModals.map((m) => {\n const targetContainer = parentGetContainer(m.modalId);\n return targetContainer && createPortal(m.element, targetContainer, m.modalId);\n })}\n {children}\n </ModalyzeContext.Provider>\n );\n }\n\n return (\n <ModalyzeContext.Provider\n value={{ instanceId, parent, rootContainer, getModalContainer: parentGetContainer }}\n >\n {levelModals.map((m) => {\n const targetContainer = parentGetContainer(m.modalId);\n return targetContainer && createPortal(m.element, targetContainer, m.modalId);\n })}\n {children}\n </ModalyzeContext.Provider>\n );\n};\n","import { useMemo } from 'react';\nimport { useModalyzeBase } from './useModalyzeBase';\nimport { getModalCloseHandler, removeModal } from '../modalStore';\n\nexport const useModalyzeInternal = () => {\n const { modalStack } = useModalyzeBase();\n return useMemo(\n () => ({\n modalStack,\n removeModal,\n getModalCloseHandler,\n }),\n [modalStack]\n );\n};\n","import { useSyncExternalStore } from 'react';\nimport { subscribeToModals, getStackSnapshot, getFocusSnapshot } from '../modalStore';\n\nexport const useModalyzeBase = () => {\n const modalStack = useSyncExternalStore(subscribeToModals, getStackSnapshot);\n const focusedModalId = useSyncExternalStore(subscribeToModals, getFocusSnapshot);\n\n return { modalStack, focusedModalId };\n};\n","import { type ComponentType, createElement, type ReactElement } from 'react';\nimport { BaseModal } from './components/BaseModal';\nimport { ModalContextWrapper } from './components/ModalContextWrapper';\nimport {\n ModalCloseHandler,\n ModalyzeCloseRequestEvent,\n} from './contexts/ModalyzeModalInternalContext';\n\ntype ModalInstance = {\n modalId: string;\n element: ReactElement;\n closeHandler: ModalCloseHandler | null;\n containerId?: string;\n};\n\nlet modalStack: ModalInstance[] = [];\nconst listeners = new Set<() => void>();\n\nlet focusedModalId: string | null = null;\n\nexport type ModalComponent<P = Record<string, unknown>> = ComponentType<P>;\n\nconst notifyListeners = () => {\n listeners.forEach((fn) => fn());\n};\n\n/**\n * Shared configuration options for modal behavior.\n * Controls dismissibility, size constraints, and positioning.\n */\nexport type ModalBehaviorConfig = {\n /** Close modal when escape is pressed and the modal is focused (default: true) */\n closeOnEscape?: boolean;\n\n /** Close modal when clicking outside the modal (default: false) */\n closeOnOutsideClick?: boolean;\n\n /** Minimum size constraints for the modal when resizing */\n minSize?: { width: number; height: number };\n\n /** Initial position of the modal in pixels from top-left of viewport */\n position?: { x: number; y: number };\n};\n\n/**\n * Configuration options for creating a modal.\n * Extends behavior config with display options like title and initial size.\n */\nexport type ModalConfig = ModalBehaviorConfig & {\n /** Title displayed in the modal's title bar */\n title?: string;\n\n /** Initial size of the modal in pixels. Modal is resizable unless disabled. */\n size?: { width: number; height: number };\n};\n\n/**\n * Complete configuration for creating a modal imperatively.\n * Extends ModalConfig with optional custom props to pass to the modal component.\n */\nexport type ModalCreationOptions<P = Record<string, unknown>> = ModalConfig & {\n props?: P;\n};\n\nexport function createModalInContainer<P extends object = Record<string, unknown>>(\n component: ModalComponent<P>,\n options?: ModalCreationOptions<P>,\n containerId?: string\n): string {\n const modalId = crypto.randomUUID();\n\n const { title, size, props, ...behaviourConfig } = options ?? {};\n const safeProps = props ?? ({} as P);\n\n const container = document.getElementById('modalyze-root');\n if (!container) {\n console.warn(\n 'Modalyze: createModal called before <Modalyze> mounted. ' +\n 'Ensure <Modalyze> is mounted before creating modals.'\n );\n return '';\n }\n\n const element = createElement(ModalContextWrapper, {\n modalId,\n ...behaviourConfig,\n children: createElement(BaseModal, { title, size }, createElement(component, safeProps)),\n });\n\n modalStack = [\n ...modalStack,\n { modalId: modalId, element: element, closeHandler: null, containerId: containerId },\n ];\n\n focusedModalId = modalId;\n notifyListeners();\n return modalId;\n}\n\nexport const removeModal = (modalId: string) => {\n modalStack = modalStack.filter((m) => m.modalId !== modalId);\n\n if (focusedModalId === modalId) {\n focusedModalId = null;\n }\n\n notifyListeners();\n};\n\nexport const getModalCloseHandler = (modalId: string) => {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (!modal) return null;\n return modal.closeHandler;\n};\n\n/**\n * Sets a close request handler for a modal. The handler is called whenever the modal\n * is about to close and can prevent closing by returning false.\n *\n * @param modalId - The ID of the modal\n * @param handler - Function called when close is requested. Return false to prevent\n * closing, true to allow. Pass null to remove an existing handler.\n *\n */\nexport function setModalCloseRequestHandler(modalId: string, handler: ModalCloseHandler | null) {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (modal) {\n modal.closeHandler = handler;\n }\n}\n\n/**\n * Closes a modal by ID. If the modal has a close request handler, it will be called\n * first and can prevent closing by returning false.\n *\n * @param modalId - The ID of the modal to close\n */\nexport const closeModal = (modalId: string) => {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (!modal) return;\n\n // Create close event\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n source: 'external',\n modalId,\n };\n\n // Check handler if exists\n if (modal.closeHandler) {\n const shouldClose = modal.closeHandler(closeEvent);\n if (!shouldClose) return;\n }\n\n removeModal(modalId);\n};\n\n/**\n * Attempts to close all modals. Close request handlers are called for each modal\n * and can prevent individual modals from closing by returning false.\n * Removals are batched so listeners are only notified once.\n */\nexport const closeAllModals = () => {\n const closableIds: string[] = [];\n\n for (const modal of [...modalStack]) {\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n source: 'external',\n modalId: modal.modalId,\n };\n\n if (modal.closeHandler) {\n const shouldClose = modal.closeHandler(closeEvent);\n if (!shouldClose) continue;\n }\n\n closableIds.push(modal.modalId);\n }\n\n if (closableIds.length === 0) return;\n\n const closableSet = new Set(closableIds);\n modalStack = modalStack.filter((m) => !closableSet.has(m.modalId));\n\n if (focusedModalId != null && closableSet.has(focusedModalId)) {\n focusedModalId = null;\n }\n\n notifyListeners();\n};\n\nexport const bringModalToFront = (modalId: string) => {\n const index = modalStack.findIndex((m) => m.modalId === modalId);\n if (index === -1) return;\n\n const modal = modalStack[index];\n modalStack = [...modalStack.filter((_, i) => i !== index), modal];\n notifyListeners();\n};\n\n/**\n * Focuses a modal and brings it to the front of the stack. Pass no argument\n * or null/undefined to unfocus all modals.\n *\n * @param modalId - The ID of the modal to focus, or omit to unfocus all modals\n */\nexport const setFocusedModal = (modalId?: string) => {\n if (modalId == null) {\n focusedModalId = null;\n } else {\n bringModalToFront(modalId);\n focusedModalId = modalId;\n }\n notifyListeners();\n};\n\nexport const getFocusedModalId = () => focusedModalId;\n\nexport const subscribeToModals = (callback: () => void) => {\n listeners.add(callback);\n return () => listeners.delete(callback);\n};\n\nexport const getStackSnapshot = () => modalStack;\nexport const getFocusSnapshot = () => focusedModalId;\n","import {\n PointerEvent as ReactPointerEvent,\n RefObject,\n useCallback,\n useEffect,\n useRef,\n} from 'react';\nimport { useDraggableState } from './useDraggableState';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\n\nexport const useDraggable = (ref: RefObject<HTMLElement | null>) => {\n const { isDragging, isDraggingRef, setDragging } = useDraggableState();\n const { setPosition } = useModalyzeModal();\n\n const clickOffsetRef = useRef({ x: 0, y: 0 });\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n event.preventDefault();\n setDragging(true);\n document.body.style.userSelect = 'none';\n event.currentTarget.setPointerCapture(event.pointerId);\n if (ref.current) {\n const rect = ref.current.getBoundingClientRect();\n clickOffsetRef.current = {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n };\n }\n },\n [ref, setDragging]\n );\n\n const onPointerUp = useCallback(() => {\n setDragging(false);\n document.body.style.userSelect = '';\n }, [setDragging]);\n\n const onPointerMove = useCallback(\n (event: PointerEvent) => {\n // Note: Passive listener, dont call preventDefault\n if (!isDraggingRef.current) return;\n\n setPosition(\n event.clientX - clickOffsetRef.current.x,\n event.clientY - clickOffsetRef.current.y\n );\n },\n [setPosition, isDraggingRef]\n );\n\n useEffect(() => {\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('pointerup', onPointerUp);\n return () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n };\n }, [onPointerMove, onPointerUp]);\n\n return {\n isDragging,\n onPointerDown,\n };\n};\n","import { useRef, useState, useCallback } from 'react';\n\nexport function useDraggableState() {\n const ref = useRef(false);\n const [state, setState] = useState(false);\n\n const setDragging = useCallback((value: boolean) => {\n ref.current = value;\n setState(value);\n }, []);\n\n return {\n isDragging: state,\n isDraggingRef: ref,\n setDragging,\n };\n}\n","import { useCallback, useContext, useMemo } from 'react';\nimport {\n closeModal,\n closeAllModals,\n createModalInContainer,\n setFocusedModal,\n setModalCloseRequestHandler,\n ModalComponent,\n bringModalToFront,\n ModalCreationOptions,\n} from '../modalStore';\nimport { useModalyzeBase } from './useModalyzeBase';\nimport { ModalyzeContext } from '../contexts/ModalyzeContext';\n\n/**\n * Public hook for managing modals from outside modal components.\n * Provides access to the modal stack state and imperative control methods.\n *\n * @returns Modal management API\n * @returns modalIds - Array of all open modal IDs in stack order (bottom to top)\n * @returns modalCount - Total number of open modals\n * @returns focusedModalId - ID of currently focused modal, or null if none focused\n * @returns frontModalId - ID of topmost modal in z-order, or null if no modals open\n * @returns createModal - Function to create a new modal imperatively\n * @returns closeModal - Function to close a specific modal by ID\n * @returns closeAllModals - Function to close all open modals\n * @returns setModalCloseRequestHandler - Function to set a close request handler for a modal\n * @returns setFocusedModal - Function to programmatically focus a modal\n * @returns bringModalToFront - Function to bring modal to the front of the modal stack\n */\nexport function useModalyze() {\n const context = useContext(ModalyzeContext);\n const instanceId = context?.instanceId;\n\n const { modalStack, focusedModalId } = useModalyzeBase();\n\n /**\n * Creates a modal imperatively while preserving React context and component scope.\n * The modal is rendered in the singleton container to maintain proper z-ordering.\n *\n * @param component - React component to render inside the modal\n * @param options - Modal configuration, with optional custom props P. All options are frozen\n * at creation time and won't update if the source object changes\n * @returns String modalId\n */\n const createModal = useCallback(\n <P extends object = Record<string, unknown>>(\n component: ModalComponent<P>,\n options?: ModalCreationOptions<P>\n ): string => {\n return createModalInContainer(component, options, instanceId);\n },\n [instanceId]\n );\n\n return useMemo(\n () => ({\n modalIds: modalStack.map((m) => m.modalId),\n modalCount: modalStack.length,\n focusedModalId,\n frontModalId: modalStack.at(-1)?.modalId ?? null,\n createModal,\n closeModal,\n closeAllModals,\n setModalCloseRequestHandler,\n setFocusedModal,\n bringModalToFront,\n }),\n [focusedModalId, modalStack, createModal]\n );\n}\n","import { createContext } from 'react';\n\nexport type ModalyzeContextType = {\n instanceId: string;\n parent: ModalyzeContextType | null;\n rootContainer: HTMLElement | null;\n getModalContainer?: (modalId: string) => HTMLElement | null;\n};\n\nexport const ModalyzeContext = createContext<ModalyzeContextType | null>(null);\n","import { useDraggable } from '../hooks/useDraggable';\nimport { useModalyze } from '../hooks/useModalyze';\nimport {\n PointerEvent as ReactPointerEvent,\n PropsWithChildren,\n useCallback,\n useEffect,\n} from 'react';\nimport { mergeClassNames } from '../Utils';\nimport { Resizer } from './Resizer';\nimport { useIsModalFocused } from '../hooks/useIsModalFocused';\nimport { ModalConfig } from '../modalStore';\nimport { useModalyzeModalInternal } from '../contexts/ModalyzeModalInternalContext';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\n\nconst DEFAULT_MODAL_SIZE = { width: 500, height: 400 };\n\nexport const BaseModal = ({\n children,\n title,\n size = DEFAULT_MODAL_SIZE,\n}: PropsWithChildren<ModalConfig>) => {\n const { modalRef, minSize } = useModalyzeModalInternal();\n const { modalId, close } = useModalyzeModal();\n const { setFocusedModal } = useModalyze();\n\n const { isDragging, onPointerDown } = useDraggable(modalRef);\n const isFocusedModal = useIsModalFocused(modalId);\n\n const clampedInitialWidth = Math.max(size.width, minSize.width);\n const clampedInitialHeight = Math.max(size.height, minSize.height);\n\n const onPointerDownHandler = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>, should_drag: boolean) => {\n event.stopPropagation();\n if (!isFocusedModal) {\n event.preventDefault();\n }\n\n setFocusedModal(modalId);\n if (should_drag) {\n onPointerDown(event);\n }\n\n modalRef.current?.focus();\n },\n [isFocusedModal, modalId, modalRef, onPointerDown, setFocusedModal]\n );\n\n useEffect(() => {\n modalRef.current?.focus();\n }, [modalRef]);\n\n useEffect(() => {\n if (!isFocusedModal) return;\n\n const element = modalRef.current;\n if (!element) return;\n\n const handleTab = (e: KeyboardEvent) => {\n // Only trap if modal is focused\n if (e.key !== 'Tab') return;\n\n const focusableElements = element.querySelectorAll(\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n );\n\n const focusableArray = Array.from(focusableElements) as HTMLElement[];\n\n if (focusableArray.length === 0) {\n e.preventDefault();\n return;\n }\n\n const firstElement = focusableArray[0];\n const lastElement = focusableArray[focusableArray.length - 1];\n\n // Shift tab on the first element, go to last\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault();\n lastElement.focus();\n }\n // Tab on last element, go to first\n else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault();\n firstElement.focus();\n }\n };\n\n element.addEventListener('keydown', handleTab);\n return () => element.removeEventListener('keydown', handleTab);\n }, [isFocusedModal, modalRef]);\n\n return (\n <div\n className={mergeClassNames(\n 'modalyze-modal',\n isFocusedModal && 'modalyze-modal-focused'\n )}\n ref={modalRef}\n tabIndex={-1}\n role=\"dialog\"\n // aria-modal=\"false\": This is a windowing system, not a blocking modal.\n // There can be multiple windows open, and users can navigate freely between them.\n aria-modal=\"false\"\n aria-labelledby={title != null ? `modal-title-${modalId}` : undefined}\n aria-describedby={`modal-content-${modalId}`}\n onPointerDown={(e) => onPointerDownHandler(e, true)}\n style={{\n width: `${clampedInitialWidth}px`,\n height: `${clampedInitialHeight}px`,\n }}\n >\n <Resizer containerRef={modalRef} />\n <div className={'modalyze-modal-content'}>\n <div\n className={mergeClassNames(\n 'modalyze-modal-header',\n isDragging && 'modalyze-modal-header-dragging'\n )}\n onPointerDown={(e) => onPointerDownHandler(e, true)}\n >\n <div\n className={'modalyze-modal-header-title'}\n id={title != null ? `modal-title-${modalId}` : undefined}\n >\n {title}\n </div>\n <button\n type={'button'}\n onClick={() => close()}\n onPointerDown={(e) => e.stopPropagation()}\n aria-label=\"Close modal\"\n className={'modalyze-modal-header-close-button'}\n >\n ×\n </button>\n </div>\n <div\n className={'modalyze-modal-body'}\n id={`modal-content-${modalId}`}\n onPointerDown={(e) => onPointerDownHandler(e, false)}\n >\n {children}\n </div>\n </div>\n </div>\n );\n};\n","export function mergeClassNames(...classes: (string | false | null | undefined)[]) {\n return classes.filter(Boolean).join(' ');\n}\n","import {\n PointerEvent as ReactPointerEvent,\n RefObject,\n useCallback,\n useEffect,\n useRef,\n} from 'react';\nimport { useContainerBounds } from '../hooks/useContainerBounds';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\nimport { POSITION_EDGE_BUFFER } from './ModalContextWrapper';\n\ntype HorizontalBar = { left: true; right?: never } | { right: true; left?: never };\n\ntype VerticalBar = { top: true; bottom?: never } | { bottom: true; top?: never };\n\ntype HorizontalBarOnly = HorizontalBar & { top?: never; bottom?: never };\ntype VerticalBarOnly = VerticalBar & { left?: never; right?: never };\ntype BothBars = HorizontalBar & VerticalBar;\n\ntype ResizerBarProps = {\n containerRef: RefObject<HTMLElement | null>;\n} & (HorizontalBarOnly | VerticalBarOnly | BothBars);\n\nconst classMap: Record<string, string> = {\n left: 'modalyze-resizer-left',\n right: 'modalyze-resizer-right',\n top: 'modalyze-resizer-top',\n bottom: 'modalyze-resizer-bottom',\n 'top-left': 'modalyze-resizer-top-left',\n 'top-right': 'modalyze-resizer-top-right',\n 'bottom-left': 'modalyze-resizer-bottom-left',\n 'bottom-right': 'modalyze-resizer-bottom-right',\n};\n\nexport const ResizerBar = ({ containerRef, left, right, top, bottom }: ResizerBarProps) => {\n const { setSize, setPosition } = useModalyzeModal();\n const containerBounds = useContainerBounds();\n const draggingRef = useRef(false);\n\n const onPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n event.preventDefault();\n event.stopPropagation();\n draggingRef.current = true;\n document.body.style.userSelect = 'none';\n }, []);\n\n const onPointerUp = useCallback(() => {\n draggingRef.current = false;\n document.body.style.userSelect = '';\n }, []);\n\n const onPointerMove = useCallback(\n (event: PointerEvent) => {\n const element = containerRef.current;\n if (!draggingRef.current || !element) return;\n\n const { clientX, clientY } = event;\n\n const rect = element.getBoundingClientRect();\n\n // Round rect values immediately to avoid fractional accumulation\n const roundedX = Math.round(\n Math.min(\n Math.max(clientX, containerBounds.left + POSITION_EDGE_BUFFER),\n containerBounds.right - POSITION_EDGE_BUFFER\n )\n );\n const roundedY = Math.round(\n Math.min(\n Math.max(clientY, containerBounds.top + POSITION_EDGE_BUFFER),\n containerBounds.bottom - POSITION_EDGE_BUFFER\n )\n );\n\n const rectLeft = Math.round(rect.left);\n const rectRight = Math.round(rect.right);\n const rectTop = Math.round(rect.top);\n const rectBottom = Math.round(rect.bottom);\n\n let updatedWidth = Math.round(rect.width);\n let updatedHeight = Math.round(rect.height);\n let translateX = rectLeft;\n let translateY = rectTop;\n\n // Horizontal resizing\n if (right) {\n const maxWidth = containerBounds.right - rectLeft;\n updatedWidth = Math.min(roundedX - rectLeft, maxWidth);\n } else if (left) {\n const proposedX = roundedX;\n const containerLeft = containerBounds.left;\n const clampedX = Math.max(proposedX, containerLeft);\n const maxWidth = rectRight - containerLeft;\n\n updatedWidth = Math.min(rectRight - clampedX, maxWidth);\n translateX = clampedX;\n }\n\n // Vertical resizing\n if (bottom) {\n const maxHeight = containerBounds.bottom - rectTop;\n updatedHeight = Math.min(roundedY - rectTop, maxHeight);\n } else if (top) {\n const proposedY = roundedY;\n const containerTop = containerBounds.top;\n const clampedY = Math.max(proposedY, containerTop);\n const maxHeight = rectBottom - containerTop;\n\n updatedHeight = Math.min(rectBottom - clampedY, maxHeight);\n translateY = clampedY;\n }\n\n const actualSize = setSize(updatedWidth, updatedHeight);\n if (!actualSize) return;\n\n // Adjust position if size was clamped on left/top edges\n if (left && actualSize.width > updatedWidth) {\n // Hit minimum width, anchor right edge\n translateX = rectRight - actualSize.width;\n }\n if (top && actualSize.height > updatedHeight) {\n // Hit minimum height, anchor bottom edge\n translateY = rectBottom - actualSize.height;\n }\n\n setPosition(translateX, translateY);\n },\n [containerRef, right, left, bottom, top, setSize, setPosition, containerBounds]\n );\n useEffect(() => {\n const element = containerRef.current;\n if (!element) return;\n const rect = element.getBoundingClientRect();\n\n const containerWidth = containerBounds.right - containerBounds.left;\n const containerHeight = containerBounds.bottom - containerBounds.top;\n\n let updatedWidth = rect.width;\n let updatedHeight = rect.height;\n\n // Note: Don't allow modals to shrink below minimum size\n if (updatedWidth > containerWidth) {\n updatedWidth = containerWidth;\n }\n if (updatedHeight > containerHeight) {\n updatedHeight = containerHeight;\n }\n\n // Only update if something changed\n if (updatedWidth !== rect.width || updatedHeight !== rect.height) {\n setSize(updatedWidth, updatedHeight);\n }\n }, [containerBounds, containerRef, setSize]);\n\n useEffect(() => {\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('pointerup', onPointerUp);\n return () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n };\n }, [onPointerMove, onPointerUp]);\n\n const axis = [top && 'top', bottom && 'bottom', left && 'left', right && 'right']\n .filter(Boolean)\n .join('-');\n\n const className = classMap[axis];\n return <div className={className} onPointerDown={onPointerDown} />;\n};\n","import { useEffect, useState } from 'react';\n\nconst getContainerBounds = () => ({\n left: 0,\n right: window.innerWidth,\n top: 0,\n bottom: window.innerHeight,\n});\n\nexport const useContainerBounds = () => {\n const [containerBounds, setContainerBounds] = useState(getContainerBounds);\n\n useEffect(() => {\n const updateBounds = () => setContainerBounds(getContainerBounds());\n window.addEventListener('resize', updateBounds);\n return () => window.removeEventListener('resize', updateBounds);\n }, []);\n\n return containerBounds;\n};\n","import { createContext, RefObject, useContext } from 'react';\n\n/**\n * Reasons why a modal close was requested.\n * Used in ModalyzeCloseRequestEvent to help handlers decide whether to allow closing.\n */\nexport const modalyzeCloseReason = {\n escape: 'escape',\n outside: 'outside',\n manual: 'manual',\n} as const;\n\nexport type ModalyzeCloseRequestEventReason =\n (typeof modalyzeCloseReason)[keyof typeof modalyzeCloseReason];\n\n/**\n * Event object passed to close request handlers when a modal is about to close.\n * Contains information about what triggered the close request, allowing handlers\n * to make informed decisions about whether to allow or prevent closing.\n *\n * @property reason - Why the close was requested (escape key, click outside, etc.)\n * @property nativeEvent - The browser event that triggered the close, if applicable\n * @property modalId - ID of the modal being closed\n * @property source - Whether close was initiated from inside the modal ('internal')\n * or outside ('external', e.g., via closeModal() or click outside)\n */\nexport interface ModalyzeCloseRequestEvent {\n reason: ModalyzeCloseRequestEventReason;\n nativeEvent?: MouseEvent | TouchEvent | KeyboardEvent;\n modalId: string;\n source: 'internal' | 'external';\n}\n\n/**\n * Handler function called when a modal is about to close.\n * Return `false` to prevent the modal from closing, or `true` to allow it.\n *\n * Useful for confirming unsaved changes, validating form state, or implementing\n * custom close logic based on how the close was triggered.\n *\n * @param event - Information about the close request (reason, source, native event)\n * @returns `true` to allow closing, `false` to prevent it\n */\nexport type ModalCloseHandler = (event: ModalyzeCloseRequestEvent) => boolean;\n\ntype ModalyzeModalInternalContextType = {\n containerRef: RefObject<HTMLDivElement | null>;\n modalRef: RefObject<HTMLDivElement | null>;\n minSize: { width: number; height: number };\n};\n\nexport const ModalyzeModalInternalContext = createContext<ModalyzeModalInternalContextType | null>(\n null\n);\n\nexport const useModalyzeModalInternal = () => {\n const ctx = useContext(ModalyzeModalInternalContext);\n if (!ctx) {\n throw new Error(\n '[Modalyze Internal Error] useModalyzeModalInternal called outside modal context. ' +\n 'This is likely a bug in Modalyze.'\n );\n }\n return ctx;\n};\n","import {\n ModalyzeCloseRequestEvent,\n modalyzeCloseReason,\n ModalyzeModalInternalContext,\n ModalCloseHandler,\n} from '../contexts/ModalyzeModalInternalContext';\n\nimport { ModalyzeModalContext } from '../contexts/ModalyzeModalContext';\n\nimport { ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';\nimport { useEscapeKey } from '../hooks/useEscapeKey';\nimport { useClickOutsideElement } from '../hooks/useClickOutsideElement';\nimport { useIsModalFocused } from '../hooks/useIsModalFocused';\nimport { useContainerBounds } from '../hooks/useContainerBounds';\nimport { ModalBehaviorConfig } from '../modalStore';\nimport { useModalyzeInternal } from '../hooks/useModalyzeInternal';\nimport { useModalyze } from '../hooks/useModalyze';\n\n// Prevent bottom/right resizers from causing scrollbars when extending beyond container.\n// Top/left overflow into negative space doesn't trigger scrollbars.\n// TODO: This should be refactored so a buffer isn't required or extracted into utility or context\nexport const POSITION_EDGE_BUFFER = 2;\n\n// Internal wrapper props (adds required fields)\ninterface ModalContextProps extends ModalBehaviorConfig {\n children: ReactNode;\n modalId: string;\n}\n\nconst DEFAULT_MIN_SIZE = { width: 300, height: 200 };\n\nexport const ModalContextWrapper = ({\n children,\n modalId,\n closeOnEscape = true,\n closeOnOutsideClick = false,\n minSize = DEFAULT_MIN_SIZE,\n position,\n}: ModalContextProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const modalRef = useRef<HTMLDivElement>(null);\n const hasInitialisedRef = useRef(false);\n const positionRef = useRef({ x: 0, y: 0 });\n\n const containerBounds = useContainerBounds();\n const { setModalCloseRequestHandler, frontModalId, setFocusedModal } = useModalyze();\n const { removeModal, getModalCloseHandler } = useModalyzeInternal();\n\n const isFocusedModal = useIsModalFocused(modalId);\n\n const isTopModal = useMemo(() => modalId === frontModalId, [modalId, frontModalId]);\n\n const setCloseRequestHandler = useCallback(\n (handler: ModalCloseHandler | null) => {\n setModalCloseRequestHandler(modalId, handler);\n },\n [modalId, setModalCloseRequestHandler]\n );\n\n const handleCloseRequest = useCallback(\n (closeEvent: ModalyzeCloseRequestEvent) => {\n const closeHandler = getModalCloseHandler(modalId);\n if (closeHandler) {\n if (closeHandler(closeEvent)) {\n removeModal(modalId);\n }\n } else {\n removeModal(modalId);\n }\n },\n [getModalCloseHandler, modalId, removeModal]\n );\n\n const escapeCloseCallback = useCallback(\n (event: KeyboardEvent) => {\n if (!closeOnEscape || !isFocusedModal) return;\n\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'escape',\n nativeEvent: event,\n modalId,\n source: 'internal',\n };\n handleCloseRequest(closeEvent);\n },\n [handleCloseRequest, closeOnEscape, isFocusedModal, modalId]\n );\n\n useEscapeKey(escapeCloseCallback);\n\n const clickOutsideCallback = useCallback(\n (event: MouseEvent | TouchEvent) => {\n setFocusedModal();\n if (!closeOnOutsideClick) return;\n\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: modalyzeCloseReason.outside,\n nativeEvent: event,\n modalId,\n source: 'internal',\n };\n\n handleCloseRequest(closeEvent);\n },\n [handleCloseRequest, closeOnOutsideClick, modalId, setFocusedModal]\n );\n useClickOutsideElement(containerRef, clickOutsideCallback);\n\n const close = useCallback(() => {\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n modalId,\n source: 'external',\n };\n handleCloseRequest(closeEvent);\n }, [handleCloseRequest, modalId]);\n\n const setSize = useCallback(\n (width: number, height: number) => {\n const element = modalRef.current;\n if (!element) return null;\n\n // Enforce minimum size\n const clampedWidth = Math.max(width, minSize.width);\n const clampedHeight = Math.max(height, minSize.height);\n\n element.style.width = `${clampedWidth}px`;\n element.style.height = `${clampedHeight}px`;\n\n return { width: clampedWidth, height: clampedHeight };\n },\n [minSize.height, minSize.width]\n );\n\n const getPositionLimits = useCallback(() => {\n const minX = containerBounds.left;\n const minY = containerBounds.top;\n\n const maxX =\n containerBounds.right - (modalRef.current?.offsetWidth ?? 0) - POSITION_EDGE_BUFFER;\n const maxY =\n containerBounds.bottom - (modalRef.current?.offsetHeight ?? 0) - POSITION_EDGE_BUFFER;\n return { minX, minY, maxX, maxY };\n }, [containerBounds]);\n\n const setPosition = useCallback(\n (x: number, y: number) => {\n const element = modalRef.current;\n if (!element) return null;\n\n const limits = getPositionLimits();\n const correctedX = Math.max(limits.minX, Math.min(x, limits.maxX));\n const correctedY = Math.max(limits.minY, Math.min(y, limits.maxY));\n\n positionRef.current = { x: correctedX, y: correctedY };\n element.style.transform = `translate(${correctedX}px, ${correctedY}px)`;\n\n return { x: correctedX, y: correctedY };\n },\n [getPositionLimits]\n );\n\n useLayoutEffect(() => {\n // On launch set modal to center of the screen\n if (modalRef.current && !hasInitialisedRef.current) {\n if (position) {\n setPosition(position.x, position.y);\n } else {\n const containerWidth = containerBounds.right - containerBounds.left;\n const containerHeight = containerBounds.bottom - containerBounds.top;\n const x =\n containerBounds.left + containerWidth / 2 - modalRef.current.offsetWidth / 2;\n const y =\n containerBounds.top + containerHeight / 2 - modalRef.current.offsetHeight / 2;\n setPosition(x, y);\n }\n hasInitialisedRef.current = true;\n }\n }, [setPosition, containerBounds, modalRef, position]);\n\n useEffect(() => {\n const { x, y } = positionRef.current;\n setPosition(x, y);\n }, [setPosition, containerBounds]);\n\n return (\n <ModalyzeModalInternalContext.Provider\n value={{\n containerRef,\n modalRef,\n minSize,\n }}\n >\n <ModalyzeModalContext.Provider\n value={{\n close,\n modalId,\n isFocusedModal,\n isTopModal,\n setCloseRequestHandler,\n setSize,\n setPosition,\n }}\n >\n <div ref={containerRef}>{children}</div>\n </ModalyzeModalContext.Provider>\n </ModalyzeModalInternalContext.Provider>\n );\n};\n","import { useEffect } from 'react';\n\nexport function useEscapeKey(onEscape: (event: KeyboardEvent) => void) {\n useEffect(() => {\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape' && !event.repeat) {\n event.stopPropagation();\n onEscape(event);\n }\n }\n\n window.addEventListener('keydown', handleKeyDown);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n };\n }, [onEscape]);\n}\n","import { RefObject, useEffect } from 'react';\n\nexport const useClickOutsideElement = (\n ref: RefObject<HTMLElement | null>,\n clickOutsideCallback: (event: MouseEvent | TouchEvent) => void\n) => {\n useEffect(() => {\n function handleOutside(event: MouseEvent | TouchEvent) {\n if (!ref.current) return;\n const target = event.target as Node;\n if (!ref.current.contains(target)) {\n clickOutsideCallback(event);\n }\n }\n\n document.addEventListener('mousedown', handleOutside);\n document.addEventListener('touchstart', handleOutside);\n\n return () => {\n document.removeEventListener('mousedown', handleOutside);\n document.removeEventListener('touchstart', handleOutside);\n };\n }, [clickOutsideCallback, ref]);\n};\n","import { useSyncExternalStore } from 'react';\nimport { getFocusedModalId, subscribeToModals } from '../modalStore';\n\nexport const useIsModalFocused = (modalId: string) => {\n const focusedModalId = useSyncExternalStore(subscribeToModals, getFocusedModalId);\n return focusedModalId === modalId;\n};\n","import { RefObject } from 'react';\nimport { ResizerBar } from './ResizerBar';\n\nexport const Resizer = ({ containerRef }: { containerRef: RefObject<HTMLElement | null> }) => {\n return (\n <>\n <ResizerBar containerRef={containerRef} left />\n <ResizerBar containerRef={containerRef} right />\n <ResizerBar containerRef={containerRef} top />\n <ResizerBar containerRef={containerRef} bottom />\n\n <ResizerBar containerRef={containerRef} top left />\n <ResizerBar containerRef={containerRef} top right />\n <ResizerBar containerRef={containerRef} bottom left />\n <ResizerBar containerRef={containerRef} bottom right />\n </>\n );\n};\n"],"mappings":"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","names":["index_exports","__export","Modalyze","useModalyze","useModalyzeModal","__toCommonJS","styleInject","css","insertAt","head","style","styleInject","import_react","ModalyzeModalContext","useModalyzeModal","ctx","import_react","import_react_dom","import_react","import_react","import_react","import_react","import_react","useDraggableState","ref","state","setState","setDragging","value","useDraggable","ref","isDragging","isDraggingRef","setDragging","useDraggableState","setPosition","useModalyzeModal","clickOffsetRef","onPointerDown","event","rect","onPointerUp","onPointerMove","import_react","import_react","ModalyzeContext","useModalyze","instanceId","ModalyzeContext","modalStack","focusedModalId","useModalyzeBase","createModal","component","options","createModalInContainer","m","closeModal","closeAllModals","setModalCloseRequestHandler","setFocusedModal","bringModalToFront","import_react","mergeClassNames","classes","import_react","import_react","getContainerBounds","useContainerBounds","containerBounds","setContainerBounds","updateBounds","import_react","modalyzeCloseReason","ModalyzeModalInternalContext","useModalyzeModalInternal","ctx","import_react","import_react","useEscapeKey","onEscape","handleKeyDown","event","import_react","useClickOutsideElement","ref","clickOutsideCallback","handleOutside","event","target","import_react","useIsModalFocused","modalId","subscribeToModals","getFocusedModalId","import_jsx_runtime","POSITION_EDGE_BUFFER","DEFAULT_MIN_SIZE","ModalContextWrapper","children","modalId","closeOnEscape","closeOnOutsideClick","minSize","position","containerRef","modalRef","hasInitialisedRef","positionRef","containerBounds","useContainerBounds","setModalCloseRequestHandler","frontModalId","setFocusedModal","useModalyze","removeModal","getModalCloseHandler","useModalyzeInternal","isFocusedModal","useIsModalFocused","isTopModal","setCloseRequestHandler","handler","handleCloseRequest","closeEvent","closeHandler","escapeCloseCallback","event","useEscapeKey","clickOutsideCallback","modalyzeCloseReason","useClickOutsideElement","close","setSize","width","height","element","clampedWidth","clampedHeight","getPositionLimits","minX","minY","maxX","maxY","setPosition","x","y","limits","correctedX","correctedY","containerWidth","containerHeight","ModalyzeModalInternalContext","ModalyzeModalContext","import_jsx_runtime","classMap","ResizerBar","containerRef","left","right","top","bottom","setSize","setPosition","useModalyzeModal","containerBounds","useContainerBounds","draggingRef","onPointerDown","event","onPointerUp","onPointerMove","element","clientX","clientY","rect","roundedX","POSITION_EDGE_BUFFER","roundedY","rectLeft","rectRight","rectTop","rectBottom","updatedWidth","updatedHeight","translateX","translateY","maxWidth","proposedX","containerLeft","clampedX","maxHeight","proposedY","containerTop","clampedY","actualSize","containerWidth","containerHeight","axis","className","import_jsx_runtime","Resizer","containerRef","ResizerBar","import_jsx_runtime","DEFAULT_MODAL_SIZE","BaseModal","children","title","size","modalRef","minSize","useModalyzeModalInternal","modalId","close","useModalyzeModal","setFocusedModal","useModalyze","isDragging","onPointerDown","useDraggable","isFocusedModal","useIsModalFocused","clampedInitialWidth","clampedInitialHeight","onPointerDownHandler","event","should_drag","element","handleTab","e","focusableElements","focusableArray","firstElement","lastElement","mergeClassNames","Resizer","modalStack","listeners","focusedModalId","notifyListeners","fn","createModalInContainer","component","options","containerId","modalId","title","size","props","behaviourConfig","safeProps","element","ModalContextWrapper","BaseModal","removeModal","m","getModalCloseHandler","modal","setModalCloseRequestHandler","handler","closeModal","closeEvent","closeAllModals","closableIds","closableSet","bringModalToFront","index","_","i","setFocusedModal","getFocusedModalId","subscribeToModals","callback","getStackSnapshot","getFocusSnapshot","useModalyzeBase","modalStack","subscribeToModals","getStackSnapshot","focusedModalId","getFocusSnapshot","useModalyzeInternal","modalStack","useModalyzeBase","removeModal","getModalCloseHandler","import_jsx_runtime","useForceUpdate","forceUpdate","x","hasRootInstance","Modalyze","children","parent","ModalyzeContext","hasParent","isRoot","instanceId","modalStack","useModalyzeInternal","container","setContainer","modalContainerRefs","div","rootContainer","levelModals","m","getModalContainer","modalId","parentGetContainer","el","targetContainer"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ function Z(e,{insertAt:o}={}){if(!e||typeof document>"u")return;let n=document.head||document.getElementsByTagName("head")[0],t=document.createElement("style");t.type="text/css",o==="top"&&n.firstChild?n.insertBefore(t,n.firstChild):n.appendChild(t),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e))}Z(`:root{--modalyze-font-family: sans-serif;--modalyze-modal-box-shadow: 0 10px 30px rgba(0, 0, 0, .2);--modalyze-modal-box-shadow-focused: 0 0 8px 2px dimgrey;--modalyze-modal-content-background-color: #fff;--modalyze-modal-header-background-color: #f5f5f5;--modalyze-modal-header-background-color-focused: #d5d5d5;--modalyze-modal-header-border-bottom-color: #ddd;--modalyze-modal-header-close-button-color: #666;--modalyze-modal-header-close-button-hover-color: #000}.modalyze-modal{position:fixed;top:0;left:0;width:400px;height:300px;display:flex;flex-direction:column;font-family:var(--modalyze-font-family);z-index:1000}.modalyze-modal-focused:after{content:"";position:absolute;inset:0;pointer-events:none;border-radius:3px;box-shadow:var(--modalyze-modal-box-shadow-focused);opacity:.9;transition:opacity .2s ease}.modalyze-modal-content{position:absolute;box-sizing:border-box;width:100%;height:100%;display:flex;overflow:hidden;flex-direction:column;background-color:var(--modalyze-modal-content-background-color);box-shadow:var(--modalyze-modal-box-shadow);border-radius:3px}.modalyze-modal-header{display:flex;align-items:center;justify-content:space-between;background-color:var(--modalyze-modal-header-background-color);padding:.75rem 1rem;cursor:grab;border-bottom:1px solid var(--modalyze-modal-header-border-bottom-color);transition:background-color .2s ease}.modalyze-modal-focused .modalyze-modal-header{background-color:var(--modalyze-modal-header-background-color-focused)}.modalyze-modal-header-dragging{cursor:grabbing}.modalyze-modal-header-title{font-weight:700;font-size:1rem;flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modalyze-modal-header-close-button{background:none;border:none;font-size:1.25rem;cursor:pointer;padding:0;margin-left:1rem;line-height:1;color:var(--modalyze-modal-header-close-button-color);transition:color .2s ease}.modalyze-modal-header-close-button:hover,.modalyze-modal-header-close-button:focus-visible{color:var(--modalyze-modal-header-close-button-hover-color)}.modalyze-modal-body{padding:1rem;overflow-y:auto;flex-grow:1}.modalyze-resizer-left{position:absolute;top:0;left:-4px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-right{position:absolute;top:0;right:-2px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-top{position:absolute;top:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-bottom{position:absolute;bottom:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-top-left{position:absolute;top:-2px;left:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}.modalyze-resizer-top-right{position:absolute;top:-2px;right:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-left{position:absolute;bottom:-2px;left:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-right{position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}
2
+ `);import{createContext as Oe,useContext as Fe}from"react";var J=Oe(null),L=()=>{let e=Fe(J);if(!e)throw new Error("useModalyzeModal must be used within a Modalyze modal");return e};import{useCallback as po,useContext as fo,useEffect as Le,useLayoutEffect as ho,useMemo as yo,useReducer as go,useRef as Mo,useState as xo}from"react";import{createPortal as ie}from"react-dom";import{useMemo as mo}from"react";import{useSyncExternalStore as Be}from"react";import{createElement as le}from"react";import{useCallback as Q,useEffect as je,useRef as Ue}from"react";import{useRef as qe,useState as Ne,useCallback as We}from"react";function ue(){let e=qe(!1),[o,n]=Ne(!1),t=We(s=>{e.current=s,n(s)},[]);return{isDragging:o,isDraggingRef:e,setDragging:t}}var me=e=>{let{isDragging:o,isDraggingRef:n,setDragging:t}=ue(),{setPosition:s}=L(),d=Ue({x:0,y:0}),g=Q(u=>{if(u.preventDefault(),t(!0),document.body.style.userSelect="none",u.currentTarget.setPointerCapture(u.pointerId),e.current){let l=e.current.getBoundingClientRect();d.current={x:u.clientX-l.left,y:u.clientY-l.top}}},[e,t]),a=Q(()=>{t(!1),document.body.style.userSelect=""},[t]),p=Q(u=>{n.current&&s(u.clientX-d.current.x,u.clientY-d.current.y)},[s,n]);return je(()=>(window.addEventListener("pointermove",p,{passive:!0}),window.addEventListener("pointerup",a),()=>{window.removeEventListener("pointermove",p),window.removeEventListener("pointerup",a)}),[p,a]),{isDragging:o,onPointerDown:g}};import{useCallback as Ye,useContext as Ke,useMemo as $e}from"react";import{createContext as Xe}from"react";var O=Xe(null);function j(){let o=Ke(O)?.instanceId,{modalStack:n,focusedModalId:t}=Y(),s=Ye((d,g)=>pe(d,g,o),[o]);return $e(()=>({modalIds:n.map(d=>d.modalId),modalCount:n.length,focusedModalId:t,frontModalId:n.at(-1)?.modalId??null,createModal:s,closeModal:he,closeAllModals:ye,setModalCloseRequestHandler:fe,setFocusedModal:ge,bringModalToFront:ee}),[t,n,s])}import{useCallback as co,useEffect as Ie}from"react";function oe(...e){return e.filter(Boolean).join(" ")}import{useCallback as re,useEffect as we,useRef as ro}from"react";import{useEffect as _e,useState as Ae}from"react";var Me=()=>({left:0,right:window.innerWidth,top:0,bottom:window.innerHeight}),K=()=>{let[e,o]=Ae(Me);return _e(()=>{let n=()=>o(Me());return window.addEventListener("resize",n),()=>window.removeEventListener("resize",n)},[]),e};import{createContext as Ve,useContext as Ge}from"react";var xe={escape:"escape",outside:"outside",manual:"manual"},te=Ve(null),be=()=>{let e=Ge(te);if(!e)throw new Error("[Modalyze Internal Error] useModalyzeModalInternal called outside modal context. This is likely a bug in Modalyze.");return e};import{useCallback as k,useEffect as eo,useLayoutEffect as oo,useMemo as to,useRef as _}from"react";import{useEffect as Ze}from"react";function ze(e){Ze(()=>{function o(n){n.key==="Escape"&&!n.repeat&&(n.stopPropagation(),e(n))}return window.addEventListener("keydown",o),()=>{window.removeEventListener("keydown",o)}},[e])}import{useEffect as Je}from"react";var ve=(e,o)=>{Je(()=>{function n(t){if(!e.current)return;let s=t.target;e.current.contains(s)||o(t)}return document.addEventListener("mousedown",n),document.addEventListener("touchstart",n),()=>{document.removeEventListener("mousedown",n),document.removeEventListener("touchstart",n)}},[o,e])};import{useSyncExternalStore as Qe}from"react";var $=e=>Qe(U,Ce)===e;import{jsx as ne}from"react/jsx-runtime";var D=2,no={width:300,height:200},Ee=({children:e,modalId:o,closeOnEscape:n=!0,closeOnOutsideClick:t=!1,minSize:s=no,position:d})=>{let g=_(null),a=_(null),p=_(!1),u=_({x:0,y:0}),l=K(),{setModalCloseRequestHandler:C,frontModalId:R,setFocusedModal:v}=j(),{removeModal:r,getModalCloseHandler:i}=A(),M=$(o),H=to(()=>o===R,[o,R]),f=k(c=>{C(o,c)},[o,C]),h=k(c=>{let m=i(o);m?m(c)&&r(o):r(o)},[i,o,r]),B=k(c=>{if(!n||!M)return;h({reason:"escape",nativeEvent:c,modalId:o,source:"internal"})},[h,n,M,o]);ze(B);let F=k(c=>{if(v(),!t)return;let m={reason:xe.outside,nativeEvent:c,modalId:o,source:"internal"};h(m)},[h,t,o,v]);ve(g,F);let q=k(()=>{h({reason:"manual",modalId:o,source:"external"})},[h,o]),N=k((c,m)=>{let b=a.current;if(!b)return null;let y=Math.max(c,s.width),z=Math.max(m,s.height);return b.style.width=`${y}px`,b.style.height=`${z}px`,{width:y,height:z}},[s.height,s.width]),S=k(()=>{let c=l.left,m=l.top,b=l.right-(a.current?.offsetWidth??0)-D,y=l.bottom-(a.current?.offsetHeight??0)-D;return{minX:c,minY:m,maxX:b,maxY:y}},[l]),E=k((c,m)=>{let b=a.current;if(!b)return null;let y=S(),z=Math.max(y.minX,Math.min(c,y.maxX)),I=Math.max(y.minY,Math.min(m,y.maxY));return u.current={x:z,y:I},b.style.transform=`translate(${z}px, ${I}px)`,{x:z,y:I}},[S]);return oo(()=>{if(a.current&&!p.current){if(d)E(d.x,d.y);else{let c=l.right-l.left,m=l.bottom-l.top,b=l.left+c/2-a.current.offsetWidth/2,y=l.top+m/2-a.current.offsetHeight/2;E(b,y)}p.current=!0}},[E,l,a,d]),eo(()=>{let{x:c,y:m}=u.current;E(c,m)},[E,l]),ne(te.Provider,{value:{containerRef:g,modalRef:a,minSize:s},children:ne(J.Provider,{value:{close:q,modalId:o,isFocusedModal:M,isTopModal:H,setCloseRequestHandler:f,setSize:N,setPosition:E},children:ne("div",{ref:g,children:e})})})};import{jsx as lo}from"react/jsx-runtime";var ao={left:"modalyze-resizer-left",right:"modalyze-resizer-right",top:"modalyze-resizer-top",bottom:"modalyze-resizer-bottom","top-left":"modalyze-resizer-top-left","top-right":"modalyze-resizer-top-right","bottom-left":"modalyze-resizer-bottom-left","bottom-right":"modalyze-resizer-bottom-right"},P=({containerRef:e,left:o,right:n,top:t,bottom:s})=>{let{setSize:d,setPosition:g}=L(),a=K(),p=ro(!1),u=re(r=>{r.preventDefault(),r.stopPropagation(),p.current=!0,document.body.style.userSelect="none"},[]),l=re(()=>{p.current=!1,document.body.style.userSelect=""},[]),C=re(r=>{let i=e.current;if(!p.current||!i)return;let{clientX:M,clientY:H}=r,f=i.getBoundingClientRect(),h=Math.round(Math.min(Math.max(M,a.left+D),a.right-D)),B=Math.round(Math.min(Math.max(H,a.top+D),a.bottom-D)),F=Math.round(f.left),q=Math.round(f.right),N=Math.round(f.top),S=Math.round(f.bottom),E=Math.round(f.width),c=Math.round(f.height),m=F,b=N;if(n){let z=a.right-F;E=Math.min(h-F,z)}else if(o){let z=h,I=a.left,W=Math.max(z,I),G=q-I;E=Math.min(q-W,G),m=W}if(s){let z=a.bottom-N;c=Math.min(B-N,z)}else if(t){let z=B,I=a.top,W=Math.max(z,I),G=S-I;c=Math.min(S-W,G),b=W}let y=d(E,c);y&&(o&&y.width>E&&(m=q-y.width),t&&y.height>c&&(b=S-y.height),g(m,b))},[e,n,o,s,t,d,g,a]);we(()=>{let r=e.current;if(!r)return;let i=r.getBoundingClientRect(),M=a.right-a.left,H=a.bottom-a.top,f=i.width,h=i.height;f>M&&(f=M),h>H&&(h=H),(f!==i.width||h!==i.height)&&d(f,h)},[a,e,d]),we(()=>(window.addEventListener("pointermove",C,{passive:!0}),window.addEventListener("pointerup",l),()=>{window.removeEventListener("pointermove",C),window.removeEventListener("pointerup",l)}),[C,l]);let R=[t&&"top",s&&"bottom",o&&"left",n&&"right"].filter(Boolean).join("-"),v=ao[R];return lo("div",{className:v,onPointerDown:u})};import{Fragment as so,jsx as T,jsxs as io}from"react/jsx-runtime";var Re=({containerRef:e})=>io(so,{children:[T(P,{containerRef:e,left:!0}),T(P,{containerRef:e,right:!0}),T(P,{containerRef:e,top:!0}),T(P,{containerRef:e,bottom:!0}),T(P,{containerRef:e,top:!0,left:!0}),T(P,{containerRef:e,top:!0,right:!0}),T(P,{containerRef:e,bottom:!0,left:!0}),T(P,{containerRef:e,bottom:!0,right:!0})]});import{jsx as V,jsxs as ae}from"react/jsx-runtime";var uo={width:500,height:400},Pe=({children:e,title:o,size:n=uo})=>{let{modalRef:t,minSize:s}=be(),{modalId:d,close:g}=L(),{setFocusedModal:a}=j(),{isDragging:p,onPointerDown:u}=me(t),l=$(d),C=Math.max(n.width,s.width),R=Math.max(n.height,s.height),v=co((r,i)=>{r.stopPropagation(),l||r.preventDefault(),a(d),i&&u(r),t.current?.focus()},[l,d,t,u,a]);return Ie(()=>{t.current?.focus()},[t]),Ie(()=>{if(!l)return;let r=t.current;if(!r)return;let i=M=>{if(M.key!=="Tab")return;let H=r.querySelectorAll('a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'),f=Array.from(H);if(f.length===0){M.preventDefault();return}let h=f[0],B=f[f.length-1];M.shiftKey&&document.activeElement===h?(M.preventDefault(),B.focus()):!M.shiftKey&&document.activeElement===B&&(M.preventDefault(),h.focus())};return r.addEventListener("keydown",i),()=>r.removeEventListener("keydown",i)},[l,t]),ae("div",{className:oe("modalyze-modal",l&&"modalyze-modal-focused"),ref:t,tabIndex:-1,role:"dialog","aria-modal":"false","aria-labelledby":o!=null?`modal-title-${d}`:void 0,"aria-describedby":`modal-content-${d}`,onPointerDown:r=>v(r,!0),style:{width:`${C}px`,height:`${R}px`},children:[V(Re,{containerRef:t}),ae("div",{className:"modalyze-modal-content",children:[ae("div",{className:oe("modalyze-modal-header",p&&"modalyze-modal-header-dragging"),onPointerDown:r=>v(r,!0),children:[V("div",{className:"modalyze-modal-header-title",id:o!=null?`modal-title-${d}`:void 0,children:o}),V("button",{type:"button",onClick:()=>g(),onPointerDown:r=>r.stopPropagation(),"aria-label":"Close modal",className:"modalyze-modal-header-close-button",children:"\xD7"})]}),V("div",{className:"modalyze-modal-body",id:`modal-content-${d}`,onPointerDown:r=>v(r,!1),children:e})]})]})};var x=[],se=new Set,w=null,X=()=>{se.forEach(e=>e())};function pe(e,o,n){let t=crypto.randomUUID(),{title:s,size:d,props:g,...a}=o??{},p=g??{};if(!document.getElementById("modalyze-root"))return console.warn("Modalyze: createModal called before <Modalyze> mounted. Ensure <Modalyze> is mounted before creating modals."),"";let l=le(Ee,{modalId:t,...a,children:le(Pe,{title:s,size:d},le(e,p))});return x=[...x,{modalId:t,element:l,closeHandler:null,containerId:n}],w=t,X(),t}var de=e=>{x=x.filter(o=>o.modalId!==e),w===e&&(w=null),X()},He=e=>{let o=x.find(n=>n.modalId===e);return o?o.closeHandler:null};function fe(e,o){let n=x.find(t=>t.modalId===e);n&&(n.closeHandler=o)}var he=e=>{let o=x.find(t=>t.modalId===e);if(!o)return;let n={reason:"manual",source:"external",modalId:e};o.closeHandler&&!o.closeHandler(n)||de(e)},ye=()=>{let e=[];for(let n of[...x]){let t={reason:"manual",source:"external",modalId:n.modalId};n.closeHandler&&!n.closeHandler(t)||e.push(n.modalId)}if(e.length===0)return;let o=new Set(e);x=x.filter(n=>!o.has(n.modalId)),w!=null&&o.has(w)&&(w=null),X()},ee=e=>{let o=x.findIndex(t=>t.modalId===e);if(o===-1)return;let n=x[o];x=[...x.filter((t,s)=>s!==o),n],X()},ge=e=>{e==null?w=null:(ee(e),w=e),X()},Ce=()=>w,U=e=>(se.add(e),()=>se.delete(e)),ke=()=>x,Te=()=>w;var Y=()=>{let e=Be(U,ke),o=Be(U,Te);return{modalStack:e,focusedModalId:o}};var A=()=>{let{modalStack:e}=Y();return mo(()=>({modalStack:e,removeModal:de,getModalCloseHandler:He}),[e])};import{Fragment as vo,jsx as De,jsxs as Se}from"react/jsx-runtime";function bo(){let[,e]=go(o=>o+1,0);return e}var ce=!1,zo=({children:e})=>{let o=fo(O),n=o!==null;Le(()=>{if(!n)return ce&&console.warn("Multiple root <Modalyze> components detected. Only mount one <Modalyze> at your app root."),ce=!0,()=>{ce=!1}},[n]);let t=o===null,s=yo(()=>crypto.randomUUID(),[]),{modalStack:d}=A(),[g,a]=xo(null),p=Mo(new Map),u=bo();Le(()=>{if(!t)return;let r=document.createElement("div");return r.id="modalyze-root",document.body.appendChild(r),a(r),()=>{document.body.removeChild(r)}},[t]),ho(()=>{t&&u()},[t,d.length,u]);let l=t?g:o.rootContainer,C=d.filter(r=>t&&r.containerId==null||r.containerId===s),R=po(r=>p.current.get(r)??null,[]);if(l==null)return null;let v=o?.getModalContainer??R;return t?Se(O.Provider,{value:{instanceId:s,parent:o,rootContainer:l,getModalContainer:R},children:[ie(De(vo,{children:d.map(r=>De("div",{ref:i=>{i?p.current.set(r.modalId,i):p.current.delete(r.modalId)},"data-modal-id":r.modalId},r.modalId))}),l),C.map(r=>{let i=v(r.modalId);return i&&ie(r.element,i,r.modalId)}),e]}):Se(O.Provider,{value:{instanceId:s,parent:o,rootContainer:l,getModalContainer:v},children:[C.map(r=>{let i=v(r.modalId);return i&&ie(r.element,i,r.modalId)}),e]})};export{zo as Modalyze,j as useModalyze,L as useModalyzeModal};
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["#style-inject:#style-inject","../src/styles.css","../src/contexts/ModalyzeModalContext.tsx","../src/components/Modalyze.tsx","../src/hooks/useModalyzeInternal.ts","../src/hooks/useModalyzeBase.ts","../src/modalStore.ts","../src/hooks/useDraggable.tsx","../src/hooks/useDraggableState.tsx","../src/hooks/useModalyze.ts","../src/contexts/ModalyzeContext.ts","../src/components/BaseModal.tsx","../src/Utils.ts","../src/components/ResizerBar.tsx","../src/hooks/useContainerBounds.tsx","../src/contexts/ModalyzeModalInternalContext.tsx","../src/components/ModalContextWrapper.tsx","../src/hooks/useEscapeKey.ts","../src/hooks/useClickOutsideElement.tsx","../src/hooks/useIsModalFocused.tsx","../src/components/Resizer.tsx"],"sourcesContent":["\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\":root{--modalyze-font-family: sans-serif;--modalyze-modal-box-shadow: 0 10px 30px rgba(0, 0, 0, .2);--modalyze-modal-box-shadow-focused: 0 0 8px 2px dimgrey;--modalyze-modal-content-background-color: #fff;--modalyze-modal-header-background-color: #f5f5f5;--modalyze-modal-header-background-color-focused: #d5d5d5;--modalyze-modal-header-border-bottom-color: #ddd;--modalyze-modal-header-close-button-color: #666;--modalyze-modal-header-close-button-hover-color: #000}.modalyze-modal{position:fixed;top:0;left:0;width:400px;height:300px;display:flex;flex-direction:column;font-family:var(--modalyze-font-family);z-index:1000}.modalyze-modal-focused:after{content:\\\"\\\";position:absolute;inset:0;pointer-events:none;border-radius:3px;box-shadow:var(--modalyze-modal-box-shadow-focused);opacity:.9;transition:opacity .2s ease}.modalyze-modal-content{position:absolute;box-sizing:border-box;width:100%;height:100%;display:flex;overflow:hidden;flex-direction:column;background-color:var(--modalyze-modal-content-background-color);box-shadow:var(--modalyze-modal-box-shadow);border-radius:3px}.modalyze-modal-header{display:flex;align-items:center;justify-content:space-between;background-color:var(--modalyze-modal-header-background-color);padding:.75rem 1rem;cursor:grab;border-bottom:1px solid var(--modalyze-modal-header-border-bottom-color);transition:background-color .2s ease}.modalyze-modal-focused .modalyze-modal-header{background-color:var(--modalyze-modal-header-background-color-focused)}.modalyze-modal-header-dragging{cursor:grabbing}.modalyze-modal-header-title{font-weight:700;font-size:1rem;flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modalyze-modal-header-close-button{background:none;border:none;font-size:1.25rem;cursor:pointer;padding:0;margin-left:1rem;line-height:1;color:var(--modalyze-modal-header-close-button-color);transition:color .2s ease}.modalyze-modal-header-close-button:hover,.modalyze-modal-header-close-button:focus-visible{color:var(--modalyze-modal-header-close-button-hover-color)}.modalyze-modal-body{padding:1rem;overflow-y:auto;flex-grow:1}.modalyze-resizer-left{position:absolute;top:0;left:-4px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-right{position:absolute;top:0;right:-2px;width:4px;height:100%;cursor:ew-resize;z-index:10;background:transparent}.modalyze-resizer-top{position:absolute;top:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-bottom{position:absolute;bottom:-2px;width:100%;height:4px;cursor:ns-resize;z-index:10;background:transparent}.modalyze-resizer-top-left{position:absolute;top:-2px;left:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}.modalyze-resizer-top-right{position:absolute;top:-2px;right:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-left{position:absolute;bottom:-2px;left:-2px;width:8px;height:8px;cursor:nesw-resize;z-index:11;background:transparent}.modalyze-resizer-bottom-right{position:absolute;bottom:-2px;right:-2px;width:8px;height:8px;cursor:nwse-resize;z-index:11;background:transparent}\\n\")","import { createContext, useContext } from 'react';\nimport { ModalCloseHandler } from './ModalyzeModalInternalContext';\n\n/**\n * Context API available to components rendered inside modals.\n * Provides modal-specific utilities like close, resize, and focus state.\n *\n * @internal This context is created automatically by Modalyze. Components should\n * use the `useModalyzeModal()` hook instead of consuming this context directly.\n */\ntype ModalyzeModalContextType = {\n /** Closes this modal */\n close: () => void;\n\n /** Unique identifier for this modal */\n modalId: string;\n\n /** Whether this modal is currently focused */\n isFocusedModal: boolean;\n\n /** Whether this modal is the top of the modal stack*/\n isTopModal: boolean;\n\n /** Sets a close request handler for this modal */\n setCloseRequestHandler: (handler: ModalCloseHandler | null) => void;\n\n /** Resizes this modal to the specified dimensions, return the updated size */\n setSize: (width: number, height: number) => { width: number; height: number } | null;\n\n /** Moves this modal to the specified coordinates, return the updated position */\n setPosition: (x: number, y: number) => { x: number; y: number } | null;\n};\n\nexport const ModalyzeModalContext = createContext<ModalyzeModalContextType | null>(null);\n\n/**\n * Hook for accessing modal-specific APIs from within a modal component.\n * Provides utilities to control the modal that contains this component.\n *\n * Note: This hook can only be used inside components rendered within a modal.\n * Use `useModalyze()` for managing modals from outside.\n *\n * @returns Modal control API for the current modal\n * @returns close - Closes this modal\n * @returns modalId - Unique identifier for this modal\n * @returns isFocusedModal - Whether this modal is currently focused\n * @returns setCloseRequestHandler - Sets a handler to intercept close requests\n * @returns setSize - Resizes this modal\n * @returns setPosition - Moves this modal\n *\n * @throws Error if used outside a modal component\n */\nexport const useModalyzeModal = () => {\n const ctx = useContext(ModalyzeModalContext);\n if (!ctx) throw new Error('useModalyzeModal must be used within a Modalyze modal');\n return ctx;\n};\n","import {\n ReactNode,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useModalyzeInternal } from '../hooks/useModalyzeInternal';\nimport { ModalyzeContext } from '../contexts/ModalyzeContext';\n\n/**\n * Returns a function that triggers a re-render without managing state.\n * Useful for synchronization patterns where you need to notify components\n * to re-check conditions (like DOM elements being ready).\n */\nfunction useForceUpdate() {\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n return forceUpdate;\n}\n\nlet hasRootInstance = false;\n\nexport const Modalyze = ({ children }: { children?: ReactNode }) => {\n const parent = useContext(ModalyzeContext);\n const hasParent = parent !== null;\n useEffect(() => {\n if (!hasParent) {\n if (hasRootInstance) {\n console.warn(\n 'Multiple root <Modalyze> components detected. ' +\n 'Only mount one <Modalyze> at your app root.'\n );\n }\n hasRootInstance = true;\n\n return () => {\n hasRootInstance = false;\n };\n }\n }, [hasParent]);\n\n const isRoot = parent === null;\n const instanceId = useMemo(() => crypto.randomUUID(), []);\n\n const { modalStack } = useModalyzeInternal();\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const modalContainerRefs = useRef<Map<string, HTMLDivElement>>(new Map());\n\n const forceUpdate = useForceUpdate();\n\n useEffect(() => {\n if (!isRoot) return;\n\n const div = document.createElement('div');\n div.id = 'modalyze-root';\n document.body.appendChild(div);\n setContainer(div);\n\n return () => {\n document.body.removeChild(div);\n };\n }, [isRoot]);\n\n /**\n * Synchronization pattern:\n * This useLayoutEffect triggers a re-render of the Modalyze component\n * (not modal contents or children) after containers are mounted in DOM.\n *\n * This allows nested instances to successfully find and portal to their containers.\n */\n useLayoutEffect(() => {\n if (!isRoot) return;\n forceUpdate();\n }, [isRoot, modalStack.length, forceUpdate]);\n\n const rootContainer = isRoot ? container : parent.rootContainer;\n\n const levelModals = modalStack.filter(\n (m) => (isRoot && m.containerId == null) || m.containerId === instanceId\n );\n\n const getModalContainer = useCallback((modalId: string) => {\n return modalContainerRefs.current.get(modalId) ?? null;\n }, []);\n\n if (rootContainer == null) return null;\n\n const parentGetContainer = parent?.getModalContainer ?? getModalContainer;\n\n if (isRoot) {\n return (\n <ModalyzeContext.Provider\n value={{ instanceId, parent, rootContainer, getModalContainer }}\n >\n {createPortal(\n <>\n {modalStack.map((m) => (\n <div\n key={m.modalId}\n ref={(el) => {\n if (el) {\n modalContainerRefs.current.set(m.modalId, el);\n } else {\n modalContainerRefs.current.delete(m.modalId);\n }\n }}\n data-modal-id={m.modalId}\n />\n ))}\n </>,\n rootContainer\n )}\n {levelModals.map((m) => {\n const targetContainer = parentGetContainer(m.modalId);\n return targetContainer && createPortal(m.element, targetContainer, m.modalId);\n })}\n {children}\n </ModalyzeContext.Provider>\n );\n }\n\n return (\n <ModalyzeContext.Provider\n value={{ instanceId, parent, rootContainer, getModalContainer: parentGetContainer }}\n >\n {levelModals.map((m) => {\n const targetContainer = parentGetContainer(m.modalId);\n return targetContainer && createPortal(m.element, targetContainer, m.modalId);\n })}\n {children}\n </ModalyzeContext.Provider>\n );\n};\n","import { useMemo } from 'react';\nimport { useModalyzeBase } from './useModalyzeBase';\nimport { getModalCloseHandler, removeModal } from '../modalStore';\n\nexport const useModalyzeInternal = () => {\n const { modalStack } = useModalyzeBase();\n return useMemo(\n () => ({\n modalStack,\n removeModal,\n getModalCloseHandler,\n }),\n [modalStack]\n );\n};\n","import { useSyncExternalStore } from 'react';\nimport { subscribeToModals, getStackSnapshot, getFocusSnapshot } from '../modalStore';\n\nexport const useModalyzeBase = () => {\n const modalStack = useSyncExternalStore(subscribeToModals, getStackSnapshot);\n const focusedModalId = useSyncExternalStore(subscribeToModals, getFocusSnapshot);\n\n return { modalStack, focusedModalId };\n};\n","import { type ComponentType, createElement, type ReactElement } from 'react';\nimport { BaseModal } from './components/BaseModal';\nimport { ModalContextWrapper } from './components/ModalContextWrapper';\nimport {\n ModalCloseHandler,\n ModalyzeCloseRequestEvent,\n} from './contexts/ModalyzeModalInternalContext';\n\ntype ModalInstance = {\n modalId: string;\n element: ReactElement;\n closeHandler: ModalCloseHandler | null;\n containerId?: string;\n};\n\nlet modalStack: ModalInstance[] = [];\nconst listeners = new Set<() => void>();\n\nlet focusedModalId: string | null = null;\n\nexport type ModalComponent<P = Record<string, unknown>> = ComponentType<P>;\n\nconst notifyListeners = () => {\n listeners.forEach((fn) => fn());\n};\n\n/**\n * Shared configuration options for modal behavior.\n * Controls dismissibility, size constraints, and positioning.\n */\nexport type ModalBehaviorConfig = {\n /** Close modal when escape is pressed and the modal is focused (default: true) */\n closeOnEscape?: boolean;\n\n /** Close modal when clicking outside the modal (default: false) */\n closeOnOutsideClick?: boolean;\n\n /** Minimum size constraints for the modal when resizing */\n minSize?: { width: number; height: number };\n\n /** Initial position of the modal in pixels from top-left of viewport */\n position?: { x: number; y: number };\n};\n\n/**\n * Configuration options for creating a modal.\n * Extends behavior config with display options like title and initial size.\n */\nexport type ModalConfig = ModalBehaviorConfig & {\n /** Title displayed in the modal's title bar */\n title?: string;\n\n /** Initial size of the modal in pixels. Modal is resizable unless disabled. */\n size?: { width: number; height: number };\n};\n\n/**\n * Complete configuration for creating a modal imperatively.\n * Extends ModalConfig with optional custom props to pass to the modal component.\n */\nexport type ModalCreationOptions<P = Record<string, unknown>> = ModalConfig & {\n props?: P;\n};\n\nexport function createModalInContainer<P extends object = Record<string, unknown>>(\n component: ModalComponent<P>,\n options?: ModalCreationOptions<P>,\n containerId?: string\n): string {\n const modalId = crypto.randomUUID();\n\n const { title, size, props, ...behaviourConfig } = options ?? {};\n const safeProps = props ?? ({} as P);\n\n const container = document.getElementById('modalyze-root');\n if (!container) {\n console.warn(\n 'Modalyze: createModal called before <Modalyze> mounted. ' +\n 'Ensure <Modalyze> is mounted before creating modals.'\n );\n return '';\n }\n\n const element = createElement(ModalContextWrapper, {\n modalId,\n ...behaviourConfig,\n children: createElement(BaseModal, { title, size }, createElement(component, safeProps)),\n });\n\n modalStack = [\n ...modalStack,\n { modalId: modalId, element: element, closeHandler: null, containerId: containerId },\n ];\n\n focusedModalId = modalId;\n notifyListeners();\n return modalId;\n}\n\nexport const removeModal = (modalId: string) => {\n modalStack = modalStack.filter((m) => m.modalId !== modalId);\n\n if (focusedModalId === modalId) {\n focusedModalId = null;\n }\n\n notifyListeners();\n};\n\nexport const getModalCloseHandler = (modalId: string) => {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (!modal) return null;\n return modal.closeHandler;\n};\n\n/**\n * Sets a close request handler for a modal. The handler is called whenever the modal\n * is about to close and can prevent closing by returning false.\n *\n * @param modalId - The ID of the modal\n * @param handler - Function called when close is requested. Return false to prevent\n * closing, true to allow. Pass null to remove an existing handler.\n *\n */\nexport function setModalCloseRequestHandler(modalId: string, handler: ModalCloseHandler | null) {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (modal) {\n modal.closeHandler = handler;\n }\n}\n\n/**\n * Closes a modal by ID. If the modal has a close request handler, it will be called\n * first and can prevent closing by returning false.\n *\n * @param modalId - The ID of the modal to close\n */\nexport const closeModal = (modalId: string) => {\n const modal = modalStack.find((m) => m.modalId === modalId);\n if (!modal) return;\n\n // Create close event\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n source: 'external',\n modalId,\n };\n\n // Check handler if exists\n if (modal.closeHandler) {\n const shouldClose = modal.closeHandler(closeEvent);\n if (!shouldClose) return;\n }\n\n removeModal(modalId);\n};\n\n/**\n * Attempts to close all modals. Close request handlers are called for each modal\n * and can prevent individual modals from closing by returning false.\n * Removals are batched so listeners are only notified once.\n */\nexport const closeAllModals = () => {\n const closableIds: string[] = [];\n\n for (const modal of [...modalStack]) {\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n source: 'external',\n modalId: modal.modalId,\n };\n\n if (modal.closeHandler) {\n const shouldClose = modal.closeHandler(closeEvent);\n if (!shouldClose) continue;\n }\n\n closableIds.push(modal.modalId);\n }\n\n if (closableIds.length === 0) return;\n\n const closableSet = new Set(closableIds);\n modalStack = modalStack.filter((m) => !closableSet.has(m.modalId));\n\n if (focusedModalId != null && closableSet.has(focusedModalId)) {\n focusedModalId = null;\n }\n\n notifyListeners();\n};\n\nexport const bringModalToFront = (modalId: string) => {\n const index = modalStack.findIndex((m) => m.modalId === modalId);\n if (index === -1) return;\n\n const modal = modalStack[index];\n modalStack = [...modalStack.filter((_, i) => i !== index), modal];\n notifyListeners();\n};\n\n/**\n * Focuses a modal and brings it to the front of the stack. Pass no argument\n * or null/undefined to unfocus all modals.\n *\n * @param modalId - The ID of the modal to focus, or omit to unfocus all modals\n */\nexport const setFocusedModal = (modalId?: string) => {\n if (modalId == null) {\n focusedModalId = null;\n } else {\n bringModalToFront(modalId);\n focusedModalId = modalId;\n }\n notifyListeners();\n};\n\nexport const getFocusedModalId = () => focusedModalId;\n\nexport const subscribeToModals = (callback: () => void) => {\n listeners.add(callback);\n return () => listeners.delete(callback);\n};\n\nexport const getStackSnapshot = () => modalStack;\nexport const getFocusSnapshot = () => focusedModalId;\n","import {\n PointerEvent as ReactPointerEvent,\n RefObject,\n useCallback,\n useEffect,\n useRef,\n} from 'react';\nimport { useDraggableState } from './useDraggableState';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\n\nexport const useDraggable = (ref: RefObject<HTMLElement | null>) => {\n const { isDragging, isDraggingRef, setDragging } = useDraggableState();\n const { setPosition } = useModalyzeModal();\n\n const clickOffsetRef = useRef({ x: 0, y: 0 });\n\n const onPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n event.preventDefault();\n setDragging(true);\n document.body.style.userSelect = 'none';\n event.currentTarget.setPointerCapture(event.pointerId);\n if (ref.current) {\n const rect = ref.current.getBoundingClientRect();\n clickOffsetRef.current = {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n };\n }\n },\n [ref, setDragging]\n );\n\n const onPointerUp = useCallback(() => {\n setDragging(false);\n document.body.style.userSelect = '';\n }, [setDragging]);\n\n const onPointerMove = useCallback(\n (event: PointerEvent) => {\n // Note: Passive listener, dont call preventDefault\n if (!isDraggingRef.current) return;\n\n setPosition(\n event.clientX - clickOffsetRef.current.x,\n event.clientY - clickOffsetRef.current.y\n );\n },\n [setPosition, isDraggingRef]\n );\n\n useEffect(() => {\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('pointerup', onPointerUp);\n return () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n };\n }, [onPointerMove, onPointerUp]);\n\n return {\n isDragging,\n onPointerDown,\n };\n};\n","import { useRef, useState, useCallback } from 'react';\n\nexport function useDraggableState() {\n const ref = useRef(false);\n const [state, setState] = useState(false);\n\n const setDragging = useCallback((value: boolean) => {\n ref.current = value;\n setState(value);\n }, []);\n\n return {\n isDragging: state,\n isDraggingRef: ref,\n setDragging,\n };\n}\n","import { useCallback, useContext, useMemo } from 'react';\nimport {\n closeModal,\n closeAllModals,\n createModalInContainer,\n setFocusedModal,\n setModalCloseRequestHandler,\n ModalComponent,\n bringModalToFront,\n ModalCreationOptions,\n} from '../modalStore';\nimport { useModalyzeBase } from './useModalyzeBase';\nimport { ModalyzeContext } from '../contexts/ModalyzeContext';\n\n/**\n * Public hook for managing modals from outside modal components.\n * Provides access to the modal stack state and imperative control methods.\n *\n * @returns Modal management API\n * @returns modalIds - Array of all open modal IDs in stack order (bottom to top)\n * @returns modalCount - Total number of open modals\n * @returns focusedModalId - ID of currently focused modal, or null if none focused\n * @returns frontModalId - ID of topmost modal in z-order, or null if no modals open\n * @returns createModal - Function to create a new modal imperatively\n * @returns closeModal - Function to close a specific modal by ID\n * @returns closeAllModals - Function to close all open modals\n * @returns setModalCloseRequestHandler - Function to set a close request handler for a modal\n * @returns setFocusedModal - Function to programmatically focus a modal\n * @returns bringModalToFront - Function to bring modal to the front of the modal stack\n */\nexport function useModalyze() {\n const context = useContext(ModalyzeContext);\n const instanceId = context?.instanceId;\n\n const { modalStack, focusedModalId } = useModalyzeBase();\n\n /**\n * Creates a modal imperatively while preserving React context and component scope.\n * The modal is rendered in the singleton container to maintain proper z-ordering.\n *\n * @param component - React component to render inside the modal\n * @param options - Modal configuration, with optional custom props P. All options are frozen\n * at creation time and won't update if the source object changes\n * @returns String modalId\n */\n const createModal = useCallback(\n <P extends object = Record<string, unknown>>(\n component: ModalComponent<P>,\n options?: ModalCreationOptions<P>\n ): string => {\n return createModalInContainer(component, options, instanceId);\n },\n [instanceId]\n );\n\n return useMemo(\n () => ({\n modalIds: modalStack.map((m) => m.modalId),\n modalCount: modalStack.length,\n focusedModalId,\n frontModalId: modalStack.at(-1)?.modalId ?? null,\n createModal,\n closeModal,\n closeAllModals,\n setModalCloseRequestHandler,\n setFocusedModal,\n bringModalToFront,\n }),\n [focusedModalId, modalStack, createModal]\n );\n}\n","import { createContext } from 'react';\n\nexport type ModalyzeContextType = {\n instanceId: string;\n parent: ModalyzeContextType | null;\n rootContainer: HTMLElement | null;\n getModalContainer?: (modalId: string) => HTMLElement | null;\n};\n\nexport const ModalyzeContext = createContext<ModalyzeContextType | null>(null);\n","import { useDraggable } from '../hooks/useDraggable';\nimport { useModalyze } from '../hooks/useModalyze';\nimport {\n PointerEvent as ReactPointerEvent,\n PropsWithChildren,\n useCallback,\n useEffect,\n} from 'react';\nimport { mergeClassNames } from '../Utils';\nimport { Resizer } from './Resizer';\nimport { useIsModalFocused } from '../hooks/useIsModalFocused';\nimport { ModalConfig } from '../modalStore';\nimport { useModalyzeModalInternal } from '../contexts/ModalyzeModalInternalContext';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\n\nconst DEFAULT_MODAL_SIZE = { width: 500, height: 400 };\n\nexport const BaseModal = ({\n children,\n title,\n size = DEFAULT_MODAL_SIZE,\n}: PropsWithChildren<ModalConfig>) => {\n const { modalRef, minSize } = useModalyzeModalInternal();\n const { modalId, close } = useModalyzeModal();\n const { setFocusedModal } = useModalyze();\n\n const { isDragging, onPointerDown } = useDraggable(modalRef);\n const isFocusedModal = useIsModalFocused(modalId);\n\n const clampedInitialWidth = Math.max(size.width, minSize.width);\n const clampedInitialHeight = Math.max(size.height, minSize.height);\n\n const onPointerDownHandler = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>, should_drag: boolean) => {\n event.stopPropagation();\n if (!isFocusedModal) {\n event.preventDefault();\n }\n\n setFocusedModal(modalId);\n if (should_drag) {\n onPointerDown(event);\n }\n\n modalRef.current?.focus();\n },\n [isFocusedModal, modalId, modalRef, onPointerDown, setFocusedModal]\n );\n\n useEffect(() => {\n modalRef.current?.focus();\n }, [modalRef]);\n\n useEffect(() => {\n if (!isFocusedModal) return;\n\n const element = modalRef.current;\n if (!element) return;\n\n const handleTab = (e: KeyboardEvent) => {\n // Only trap if modal is focused\n if (e.key !== 'Tab') return;\n\n const focusableElements = element.querySelectorAll(\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n );\n\n const focusableArray = Array.from(focusableElements) as HTMLElement[];\n\n if (focusableArray.length === 0) {\n e.preventDefault();\n return;\n }\n\n const firstElement = focusableArray[0];\n const lastElement = focusableArray[focusableArray.length - 1];\n\n // Shift tab on the first element, go to last\n if (e.shiftKey && document.activeElement === firstElement) {\n e.preventDefault();\n lastElement.focus();\n }\n // Tab on last element, go to first\n else if (!e.shiftKey && document.activeElement === lastElement) {\n e.preventDefault();\n firstElement.focus();\n }\n };\n\n element.addEventListener('keydown', handleTab);\n return () => element.removeEventListener('keydown', handleTab);\n }, [isFocusedModal, modalRef]);\n\n return (\n <div\n className={mergeClassNames(\n 'modalyze-modal',\n isFocusedModal && 'modalyze-modal-focused'\n )}\n ref={modalRef}\n tabIndex={-1}\n role=\"dialog\"\n // aria-modal=\"false\": This is a windowing system, not a blocking modal.\n // There can be multiple windows open, and users can navigate freely between them.\n aria-modal=\"false\"\n aria-labelledby={title != null ? `modal-title-${modalId}` : undefined}\n aria-describedby={`modal-content-${modalId}`}\n onPointerDown={(e) => onPointerDownHandler(e, true)}\n style={{\n width: `${clampedInitialWidth}px`,\n height: `${clampedInitialHeight}px`,\n }}\n >\n <Resizer containerRef={modalRef} />\n <div className={'modalyze-modal-content'}>\n <div\n className={mergeClassNames(\n 'modalyze-modal-header',\n isDragging && 'modalyze-modal-header-dragging'\n )}\n onPointerDown={(e) => onPointerDownHandler(e, true)}\n >\n <div\n className={'modalyze-modal-header-title'}\n id={title != null ? `modal-title-${modalId}` : undefined}\n >\n {title}\n </div>\n <button\n type={'button'}\n onClick={() => close()}\n onPointerDown={(e) => e.stopPropagation()}\n aria-label=\"Close modal\"\n className={'modalyze-modal-header-close-button'}\n >\n ×\n </button>\n </div>\n <div\n className={'modalyze-modal-body'}\n id={`modal-content-${modalId}`}\n onPointerDown={(e) => onPointerDownHandler(e, false)}\n >\n {children}\n </div>\n </div>\n </div>\n );\n};\n","export function mergeClassNames(...classes: (string | false | null | undefined)[]) {\n return classes.filter(Boolean).join(' ');\n}\n","import {\n PointerEvent as ReactPointerEvent,\n RefObject,\n useCallback,\n useEffect,\n useRef,\n} from 'react';\nimport { useContainerBounds } from '../hooks/useContainerBounds';\nimport { useModalyzeModal } from '../contexts/ModalyzeModalContext';\nimport { POSITION_EDGE_BUFFER } from './ModalContextWrapper';\n\ntype HorizontalBar = { left: true; right?: never } | { right: true; left?: never };\n\ntype VerticalBar = { top: true; bottom?: never } | { bottom: true; top?: never };\n\ntype HorizontalBarOnly = HorizontalBar & { top?: never; bottom?: never };\ntype VerticalBarOnly = VerticalBar & { left?: never; right?: never };\ntype BothBars = HorizontalBar & VerticalBar;\n\ntype ResizerBarProps = {\n containerRef: RefObject<HTMLElement | null>;\n} & (HorizontalBarOnly | VerticalBarOnly | BothBars);\n\nconst classMap: Record<string, string> = {\n left: 'modalyze-resizer-left',\n right: 'modalyze-resizer-right',\n top: 'modalyze-resizer-top',\n bottom: 'modalyze-resizer-bottom',\n 'top-left': 'modalyze-resizer-top-left',\n 'top-right': 'modalyze-resizer-top-right',\n 'bottom-left': 'modalyze-resizer-bottom-left',\n 'bottom-right': 'modalyze-resizer-bottom-right',\n};\n\nexport const ResizerBar = ({ containerRef, left, right, top, bottom }: ResizerBarProps) => {\n const { setSize, setPosition } = useModalyzeModal();\n const containerBounds = useContainerBounds();\n const draggingRef = useRef(false);\n\n const onPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n event.preventDefault();\n event.stopPropagation();\n draggingRef.current = true;\n document.body.style.userSelect = 'none';\n }, []);\n\n const onPointerUp = useCallback(() => {\n draggingRef.current = false;\n document.body.style.userSelect = '';\n }, []);\n\n const onPointerMove = useCallback(\n (event: PointerEvent) => {\n const element = containerRef.current;\n if (!draggingRef.current || !element) return;\n\n const { clientX, clientY } = event;\n\n const rect = element.getBoundingClientRect();\n\n // Round rect values immediately to avoid fractional accumulation\n const roundedX = Math.round(\n Math.min(\n Math.max(clientX, containerBounds.left + POSITION_EDGE_BUFFER),\n containerBounds.right - POSITION_EDGE_BUFFER\n )\n );\n const roundedY = Math.round(\n Math.min(\n Math.max(clientY, containerBounds.top + POSITION_EDGE_BUFFER),\n containerBounds.bottom - POSITION_EDGE_BUFFER\n )\n );\n\n const rectLeft = Math.round(rect.left);\n const rectRight = Math.round(rect.right);\n const rectTop = Math.round(rect.top);\n const rectBottom = Math.round(rect.bottom);\n\n let updatedWidth = Math.round(rect.width);\n let updatedHeight = Math.round(rect.height);\n let translateX = rectLeft;\n let translateY = rectTop;\n\n // Horizontal resizing\n if (right) {\n const maxWidth = containerBounds.right - rectLeft;\n updatedWidth = Math.min(roundedX - rectLeft, maxWidth);\n } else if (left) {\n const proposedX = roundedX;\n const containerLeft = containerBounds.left;\n const clampedX = Math.max(proposedX, containerLeft);\n const maxWidth = rectRight - containerLeft;\n\n updatedWidth = Math.min(rectRight - clampedX, maxWidth);\n translateX = clampedX;\n }\n\n // Vertical resizing\n if (bottom) {\n const maxHeight = containerBounds.bottom - rectTop;\n updatedHeight = Math.min(roundedY - rectTop, maxHeight);\n } else if (top) {\n const proposedY = roundedY;\n const containerTop = containerBounds.top;\n const clampedY = Math.max(proposedY, containerTop);\n const maxHeight = rectBottom - containerTop;\n\n updatedHeight = Math.min(rectBottom - clampedY, maxHeight);\n translateY = clampedY;\n }\n\n const actualSize = setSize(updatedWidth, updatedHeight);\n if (!actualSize) return;\n\n // Adjust position if size was clamped on left/top edges\n if (left && actualSize.width > updatedWidth) {\n // Hit minimum width, anchor right edge\n translateX = rectRight - actualSize.width;\n }\n if (top && actualSize.height > updatedHeight) {\n // Hit minimum height, anchor bottom edge\n translateY = rectBottom - actualSize.height;\n }\n\n setPosition(translateX, translateY);\n },\n [containerRef, right, left, bottom, top, setSize, setPosition, containerBounds]\n );\n useEffect(() => {\n const element = containerRef.current;\n if (!element) return;\n const rect = element.getBoundingClientRect();\n\n const containerWidth = containerBounds.right - containerBounds.left;\n const containerHeight = containerBounds.bottom - containerBounds.top;\n\n let updatedWidth = rect.width;\n let updatedHeight = rect.height;\n\n // Note: Don't allow modals to shrink below minimum size\n if (updatedWidth > containerWidth) {\n updatedWidth = containerWidth;\n }\n if (updatedHeight > containerHeight) {\n updatedHeight = containerHeight;\n }\n\n // Only update if something changed\n if (updatedWidth !== rect.width || updatedHeight !== rect.height) {\n setSize(updatedWidth, updatedHeight);\n }\n }, [containerBounds, containerRef, setSize]);\n\n useEffect(() => {\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('pointerup', onPointerUp);\n return () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n };\n }, [onPointerMove, onPointerUp]);\n\n const axis = [top && 'top', bottom && 'bottom', left && 'left', right && 'right']\n .filter(Boolean)\n .join('-');\n\n const className = classMap[axis];\n return <div className={className} onPointerDown={onPointerDown} />;\n};\n","import { useEffect, useState } from 'react';\n\nconst getContainerBounds = () => ({\n left: 0,\n right: window.innerWidth,\n top: 0,\n bottom: window.innerHeight,\n});\n\nexport const useContainerBounds = () => {\n const [containerBounds, setContainerBounds] = useState(getContainerBounds);\n\n useEffect(() => {\n const updateBounds = () => setContainerBounds(getContainerBounds());\n window.addEventListener('resize', updateBounds);\n return () => window.removeEventListener('resize', updateBounds);\n }, []);\n\n return containerBounds;\n};\n","import { createContext, RefObject, useContext } from 'react';\n\n/**\n * Reasons why a modal close was requested.\n * Used in ModalyzeCloseRequestEvent to help handlers decide whether to allow closing.\n */\nexport const modalyzeCloseReason = {\n escape: 'escape',\n outside: 'outside',\n manual: 'manual',\n} as const;\n\nexport type ModalyzeCloseRequestEventReason =\n (typeof modalyzeCloseReason)[keyof typeof modalyzeCloseReason];\n\n/**\n * Event object passed to close request handlers when a modal is about to close.\n * Contains information about what triggered the close request, allowing handlers\n * to make informed decisions about whether to allow or prevent closing.\n *\n * @property reason - Why the close was requested (escape key, click outside, etc.)\n * @property nativeEvent - The browser event that triggered the close, if applicable\n * @property modalId - ID of the modal being closed\n * @property source - Whether close was initiated from inside the modal ('internal')\n * or outside ('external', e.g., via closeModal() or click outside)\n */\nexport interface ModalyzeCloseRequestEvent {\n reason: ModalyzeCloseRequestEventReason;\n nativeEvent?: MouseEvent | TouchEvent | KeyboardEvent;\n modalId: string;\n source: 'internal' | 'external';\n}\n\n/**\n * Handler function called when a modal is about to close.\n * Return `false` to prevent the modal from closing, or `true` to allow it.\n *\n * Useful for confirming unsaved changes, validating form state, or implementing\n * custom close logic based on how the close was triggered.\n *\n * @param event - Information about the close request (reason, source, native event)\n * @returns `true` to allow closing, `false` to prevent it\n */\nexport type ModalCloseHandler = (event: ModalyzeCloseRequestEvent) => boolean;\n\ntype ModalyzeModalInternalContextType = {\n containerRef: RefObject<HTMLDivElement | null>;\n modalRef: RefObject<HTMLDivElement | null>;\n minSize: { width: number; height: number };\n};\n\nexport const ModalyzeModalInternalContext = createContext<ModalyzeModalInternalContextType | null>(\n null\n);\n\nexport const useModalyzeModalInternal = () => {\n const ctx = useContext(ModalyzeModalInternalContext);\n if (!ctx) {\n throw new Error(\n '[Modalyze Internal Error] useModalyzeModalInternal called outside modal context. ' +\n 'This is likely a bug in Modalyze.'\n );\n }\n return ctx;\n};\n","import {\n ModalyzeCloseRequestEvent,\n modalyzeCloseReason,\n ModalyzeModalInternalContext,\n ModalCloseHandler,\n} from '../contexts/ModalyzeModalInternalContext';\n\nimport { ModalyzeModalContext } from '../contexts/ModalyzeModalContext';\n\nimport { ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';\nimport { useEscapeKey } from '../hooks/useEscapeKey';\nimport { useClickOutsideElement } from '../hooks/useClickOutsideElement';\nimport { useIsModalFocused } from '../hooks/useIsModalFocused';\nimport { useContainerBounds } from '../hooks/useContainerBounds';\nimport { ModalBehaviorConfig } from '../modalStore';\nimport { useModalyzeInternal } from '../hooks/useModalyzeInternal';\nimport { useModalyze } from '../hooks/useModalyze';\n\n// Prevent bottom/right resizers from causing scrollbars when extending beyond container.\n// Top/left overflow into negative space doesn't trigger scrollbars.\n// TODO: This should be refactored so a buffer isn't required or extracted into utility or context\nexport const POSITION_EDGE_BUFFER = 2;\n\n// Internal wrapper props (adds required fields)\ninterface ModalContextProps extends ModalBehaviorConfig {\n children: ReactNode;\n modalId: string;\n}\n\nconst DEFAULT_MIN_SIZE = { width: 300, height: 200 };\n\nexport const ModalContextWrapper = ({\n children,\n modalId,\n closeOnEscape = true,\n closeOnOutsideClick = false,\n minSize = DEFAULT_MIN_SIZE,\n position,\n}: ModalContextProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const modalRef = useRef<HTMLDivElement>(null);\n const hasInitialisedRef = useRef(false);\n const positionRef = useRef({ x: 0, y: 0 });\n\n const containerBounds = useContainerBounds();\n const { setModalCloseRequestHandler, frontModalId, setFocusedModal } = useModalyze();\n const { removeModal, getModalCloseHandler } = useModalyzeInternal();\n\n const isFocusedModal = useIsModalFocused(modalId);\n\n const isTopModal = useMemo(() => modalId === frontModalId, [modalId, frontModalId]);\n\n const setCloseRequestHandler = useCallback(\n (handler: ModalCloseHandler | null) => {\n setModalCloseRequestHandler(modalId, handler);\n },\n [modalId, setModalCloseRequestHandler]\n );\n\n const handleCloseRequest = useCallback(\n (closeEvent: ModalyzeCloseRequestEvent) => {\n const closeHandler = getModalCloseHandler(modalId);\n if (closeHandler) {\n if (closeHandler(closeEvent)) {\n removeModal(modalId);\n }\n } else {\n removeModal(modalId);\n }\n },\n [getModalCloseHandler, modalId, removeModal]\n );\n\n const escapeCloseCallback = useCallback(\n (event: KeyboardEvent) => {\n if (!closeOnEscape || !isFocusedModal) return;\n\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'escape',\n nativeEvent: event,\n modalId,\n source: 'internal',\n };\n handleCloseRequest(closeEvent);\n },\n [handleCloseRequest, closeOnEscape, isFocusedModal, modalId]\n );\n\n useEscapeKey(escapeCloseCallback);\n\n const clickOutsideCallback = useCallback(\n (event: MouseEvent | TouchEvent) => {\n setFocusedModal();\n if (!closeOnOutsideClick) return;\n\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: modalyzeCloseReason.outside,\n nativeEvent: event,\n modalId,\n source: 'internal',\n };\n\n handleCloseRequest(closeEvent);\n },\n [handleCloseRequest, closeOnOutsideClick, modalId, setFocusedModal]\n );\n useClickOutsideElement(containerRef, clickOutsideCallback);\n\n const close = useCallback(() => {\n const closeEvent: ModalyzeCloseRequestEvent = {\n reason: 'manual',\n modalId,\n source: 'external',\n };\n handleCloseRequest(closeEvent);\n }, [handleCloseRequest, modalId]);\n\n const setSize = useCallback(\n (width: number, height: number) => {\n const element = modalRef.current;\n if (!element) return null;\n\n // Enforce minimum size\n const clampedWidth = Math.max(width, minSize.width);\n const clampedHeight = Math.max(height, minSize.height);\n\n element.style.width = `${clampedWidth}px`;\n element.style.height = `${clampedHeight}px`;\n\n return { width: clampedWidth, height: clampedHeight };\n },\n [minSize.height, minSize.width]\n );\n\n const getPositionLimits = useCallback(() => {\n const minX = containerBounds.left;\n const minY = containerBounds.top;\n\n const maxX =\n containerBounds.right - (modalRef.current?.offsetWidth ?? 0) - POSITION_EDGE_BUFFER;\n const maxY =\n containerBounds.bottom - (modalRef.current?.offsetHeight ?? 0) - POSITION_EDGE_BUFFER;\n return { minX, minY, maxX, maxY };\n }, [containerBounds]);\n\n const setPosition = useCallback(\n (x: number, y: number) => {\n const element = modalRef.current;\n if (!element) return null;\n\n const limits = getPositionLimits();\n const correctedX = Math.max(limits.minX, Math.min(x, limits.maxX));\n const correctedY = Math.max(limits.minY, Math.min(y, limits.maxY));\n\n positionRef.current = { x: correctedX, y: correctedY };\n element.style.transform = `translate(${correctedX}px, ${correctedY}px)`;\n\n return { x: correctedX, y: correctedY };\n },\n [getPositionLimits]\n );\n\n useLayoutEffect(() => {\n // On launch set modal to center of the screen\n if (modalRef.current && !hasInitialisedRef.current) {\n if (position) {\n setPosition(position.x, position.y);\n } else {\n const containerWidth = containerBounds.right - containerBounds.left;\n const containerHeight = containerBounds.bottom - containerBounds.top;\n const x =\n containerBounds.left + containerWidth / 2 - modalRef.current.offsetWidth / 2;\n const y =\n containerBounds.top + containerHeight / 2 - modalRef.current.offsetHeight / 2;\n setPosition(x, y);\n }\n hasInitialisedRef.current = true;\n }\n }, [setPosition, containerBounds, modalRef, position]);\n\n useEffect(() => {\n const { x, y } = positionRef.current;\n setPosition(x, y);\n }, [setPosition, containerBounds]);\n\n return (\n <ModalyzeModalInternalContext.Provider\n value={{\n containerRef,\n modalRef,\n minSize,\n }}\n >\n <ModalyzeModalContext.Provider\n value={{\n close,\n modalId,\n isFocusedModal,\n isTopModal,\n setCloseRequestHandler,\n setSize,\n setPosition,\n }}\n >\n <div ref={containerRef}>{children}</div>\n </ModalyzeModalContext.Provider>\n </ModalyzeModalInternalContext.Provider>\n );\n};\n","import { useEffect } from 'react';\n\nexport function useEscapeKey(onEscape: (event: KeyboardEvent) => void) {\n useEffect(() => {\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape' && !event.repeat) {\n event.stopPropagation();\n onEscape(event);\n }\n }\n\n window.addEventListener('keydown', handleKeyDown);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n };\n }, [onEscape]);\n}\n","import { RefObject, useEffect } from 'react';\n\nexport const useClickOutsideElement = (\n ref: RefObject<HTMLElement | null>,\n clickOutsideCallback: (event: MouseEvent | TouchEvent) => void\n) => {\n useEffect(() => {\n function handleOutside(event: MouseEvent | TouchEvent) {\n if (!ref.current) return;\n const target = event.target as Node;\n if (!ref.current.contains(target)) {\n clickOutsideCallback(event);\n }\n }\n\n document.addEventListener('mousedown', handleOutside);\n document.addEventListener('touchstart', handleOutside);\n\n return () => {\n document.removeEventListener('mousedown', handleOutside);\n document.removeEventListener('touchstart', handleOutside);\n };\n }, [clickOutsideCallback, ref]);\n};\n","import { useSyncExternalStore } from 'react';\nimport { getFocusedModalId, subscribeToModals } from '../modalStore';\n\nexport const useIsModalFocused = (modalId: string) => {\n const focusedModalId = useSyncExternalStore(subscribeToModals, getFocusedModalId);\n return focusedModalId === modalId;\n};\n","import { RefObject } from 'react';\nimport { ResizerBar } from './ResizerBar';\n\nexport const Resizer = ({ containerRef }: { containerRef: RefObject<HTMLElement | null> }) => {\n return (\n <>\n <ResizerBar containerRef={containerRef} left />\n <ResizerBar containerRef={containerRef} right />\n <ResizerBar containerRef={containerRef} top />\n <ResizerBar containerRef={containerRef} bottom />\n\n <ResizerBar containerRef={containerRef} top left />\n <ResizerBar containerRef={containerRef} top right />\n <ResizerBar containerRef={containerRef} bottom left />\n <ResizerBar containerRef={containerRef} bottom right />\n </>\n );\n};\n"],"mappings":"AACyB,SAARA,EAA6BC,EAAK,CAAE,SAAAC,CAAS,EAAI,CAAC,EAAG,CAC1D,GAAI,CAACD,GAAO,OAAO,SAAa,IAAa,OAE7C,IAAME,EAAO,SAAS,MAAQ,SAAS,qBAAqB,MAAM,EAAE,CAAC,EAC/DC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,KAAO,WAETF,IAAa,OACXC,EAAK,WACPA,EAAK,aAAaC,EAAOD,EAAK,UAAU,EAK1CA,EAAK,YAAYC,CAAK,EAGpBA,EAAM,WACRA,EAAM,WAAW,QAAUH,EAE3BG,EAAM,YAAY,SAAS,eAAeH,CAAG,CAAC,CAElD,CCvB8BI,EAAY;AAAA,CAAonG,ECAxqG,OAAS,iBAAAC,GAAe,cAAAC,OAAkB,QAiCnC,IAAMC,EAAuBF,GAA+C,IAAI,EAmB1EG,EAAmB,IAAM,CAClC,IAAMC,EAAMH,GAAWC,CAAoB,EAC3C,GAAI,CAACE,EAAK,MAAM,IAAI,MAAM,uDAAuD,EACjF,OAAOA,CACX,ECxDA,OAEI,eAAAC,GACA,cAAAC,GACA,aAAAC,GACA,mBAAAC,GACA,WAAAC,GACA,cAAAC,GACA,UAAAC,GACA,YAAAC,OACG,QACP,OAAS,gBAAAC,OAAoB,YCX7B,OAAS,WAAAC,OAAe,QCAxB,OAAS,wBAAAC,OAA4B,QCArC,OAA6B,iBAAAC,OAAwC,QCArE,OAGI,eAAAC,EACA,aAAAC,GACA,UAAAC,OACG,QCNP,OAAS,UAAAC,GAAQ,YAAAC,GAAU,eAAAC,OAAmB,QAEvC,SAASC,IAAoB,CAChC,IAAMC,EAAMJ,GAAO,EAAK,EAClB,CAACK,EAAOC,CAAQ,EAAIL,GAAS,EAAK,EAElCM,EAAcL,GAAaM,GAAmB,CAChDJ,EAAI,QAAUI,EACdF,EAASE,CAAK,CAClB,EAAG,CAAC,CAAC,EAEL,MAAO,CACH,WAAYH,EACZ,cAAeD,EACf,YAAAG,CACJ,CACJ,CDNO,IAAME,GAAgBC,GAAuC,CAChE,GAAM,CAAE,WAAAC,EAAY,cAAAC,EAAe,YAAAC,CAAY,EAAIC,GAAkB,EAC/D,CAAE,YAAAC,CAAY,EAAIC,EAAiB,EAEnCC,EAAiBC,GAAO,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,EAEtCC,EAAgBC,EACjBC,GAA6C,CAK1C,GAJAA,EAAM,eAAe,EACrBR,EAAY,EAAI,EAChB,SAAS,KAAK,MAAM,WAAa,OACjCQ,EAAM,cAAc,kBAAkBA,EAAM,SAAS,EACjDX,EAAI,QAAS,CACb,IAAMY,EAAOZ,EAAI,QAAQ,sBAAsB,EAC/CO,EAAe,QAAU,CACrB,EAAGI,EAAM,QAAUC,EAAK,KACxB,EAAGD,EAAM,QAAUC,EAAK,GAC5B,CACJ,CACJ,EACA,CAACZ,EAAKG,CAAW,CACrB,EAEMU,EAAcH,EAAY,IAAM,CAClCP,EAAY,EAAK,EACjB,SAAS,KAAK,MAAM,WAAa,EACrC,EAAG,CAACA,CAAW,CAAC,EAEVW,EAAgBJ,EACjBC,GAAwB,CAEhBT,EAAc,SAEnBG,EACIM,EAAM,QAAUJ,EAAe,QAAQ,EACvCI,EAAM,QAAUJ,EAAe,QAAQ,CAC3C,CACJ,EACA,CAACF,EAAaH,CAAa,CAC/B,EAEA,OAAAa,GAAU,KACN,OAAO,iBAAiB,cAAeD,EAAe,CAAE,QAAS,EAAK,CAAC,EACvE,OAAO,iBAAiB,YAAaD,CAAW,EACzC,IAAM,CACT,OAAO,oBAAoB,cAAeC,CAAa,EACvD,OAAO,oBAAoB,YAAaD,CAAW,CACvD,GACD,CAACC,EAAeD,CAAW,CAAC,EAExB,CACH,WAAAZ,EACA,cAAAQ,CACJ,CACJ,EEhEA,OAAS,eAAAO,GAAa,cAAAC,GAAY,WAAAC,OAAe,QCAjD,OAAS,iBAAAC,OAAqB,QASvB,IAAMC,EAAkBD,GAA0C,IAAI,EDqBtE,SAASE,GAAc,CAE1B,IAAMC,EADUC,GAAWC,CAAe,GACd,WAEtB,CAAE,WAAAC,EAAY,eAAAC,CAAe,EAAIC,EAAgB,EAWjDC,EAAcC,GAChB,CACIC,EACAC,IAEOC,GAAuBF,EAAWC,EAAST,CAAU,EAEhE,CAACA,CAAU,CACf,EAEA,OAAOW,GACH,KAAO,CACH,SAAUR,EAAW,IAAKS,GAAMA,EAAE,OAAO,EACzC,WAAYT,EAAW,OACvB,eAAAC,EACA,aAAcD,EAAW,GAAG,EAAE,GAAG,SAAW,KAC5C,YAAAG,EACA,WAAAO,GACA,eAAAC,GACA,4BAAAC,GACA,gBAAAC,GACA,kBAAAC,EACJ,GACA,CAACb,EAAgBD,EAAYG,CAAW,CAC5C,CACJ,CEpEA,OAGI,eAAAY,GACA,aAAAC,OACG,QCPA,SAASC,MAAmBC,EAAgD,CAC/E,OAAOA,EAAQ,OAAO,OAAO,EAAE,KAAK,GAAG,CAC3C,CCFA,OAGI,eAAAC,GACA,aAAAC,GACA,UAAAC,OACG,QCNP,OAAS,aAAAC,GAAW,YAAAC,OAAgB,QAEpC,IAAMC,GAAqB,KAAO,CAC9B,KAAM,EACN,MAAO,OAAO,WACd,IAAK,EACL,OAAQ,OAAO,WACnB,GAEaC,EAAqB,IAAM,CACpC,GAAM,CAACC,EAAiBC,CAAkB,EAAIJ,GAASC,EAAkB,EAEzE,OAAAF,GAAU,IAAM,CACZ,IAAMM,EAAe,IAAMD,EAAmBH,GAAmB,CAAC,EAClE,cAAO,iBAAiB,SAAUI,CAAY,EACvC,IAAM,OAAO,oBAAoB,SAAUA,CAAY,CAClE,EAAG,CAAC,CAAC,EAEEF,CACX,ECnBA,OAAS,iBAAAG,GAA0B,cAAAC,OAAkB,QAM9C,IAAMC,GAAsB,CAC/B,OAAQ,SACR,QAAS,UACT,OAAQ,QACZ,EAyCaC,GAA+BH,GACxC,IACJ,EAEaI,GAA2B,IAAM,CAC1C,IAAMC,EAAMJ,GAAWE,EAA4B,EACnD,GAAI,CAACE,EACD,MAAM,IAAI,MACN,oHAEJ,EAEJ,OAAOA,CACX,ECvDA,OAAoB,eAAAC,EAAa,aAAAC,GAAW,mBAAAC,GAAiB,WAAAC,GAAS,UAAAC,MAAc,QCTpF,OAAS,aAAAC,OAAiB,QAEnB,SAASC,GAAaC,EAA0C,CACnEF,GAAU,IAAM,CACZ,SAASG,EAAcC,EAAsB,CACrCA,EAAM,MAAQ,UAAY,CAACA,EAAM,SACjCA,EAAM,gBAAgB,EACtBF,EAASE,CAAK,EAEtB,CAEA,cAAO,iBAAiB,UAAWD,CAAa,EACzC,IAAM,CACT,OAAO,oBAAoB,UAAWA,CAAa,CACvD,CACJ,EAAG,CAACD,CAAQ,CAAC,CACjB,CChBA,OAAoB,aAAAG,OAAiB,QAE9B,IAAMC,GAAyB,CAClCC,EACAC,IACC,CACDH,GAAU,IAAM,CACZ,SAASI,EAAcC,EAAgC,CACnD,GAAI,CAACH,EAAI,QAAS,OAClB,IAAMI,EAASD,EAAM,OAChBH,EAAI,QAAQ,SAASI,CAAM,GAC5BH,EAAqBE,CAAK,CAElC,CAEA,gBAAS,iBAAiB,YAAaD,CAAa,EACpD,SAAS,iBAAiB,aAAcA,CAAa,EAE9C,IAAM,CACT,SAAS,oBAAoB,YAAaA,CAAa,EACvD,SAAS,oBAAoB,aAAcA,CAAa,CAC5D,CACJ,EAAG,CAACD,EAAsBD,CAAG,CAAC,CAClC,ECvBA,OAAS,wBAAAK,OAA4B,QAG9B,IAAMC,EAAqBC,GACPC,GAAqBC,EAAmBC,EAAiB,IACtDH,EHuMd,cAAAI,OAAA,oBAvLT,IAAMC,EAAuB,EAQ9BC,GAAmB,CAAE,MAAO,IAAK,OAAQ,GAAI,EAEtCC,GAAsB,CAAC,CAChC,SAAAC,EACA,QAAAC,EACA,cAAAC,EAAgB,GAChB,oBAAAC,EAAsB,GACtB,QAAAC,EAAUN,GACV,SAAAO,CACJ,IAAyB,CACrB,IAAMC,EAAeC,EAAuB,IAAI,EAC1CC,EAAWD,EAAuB,IAAI,EACtCE,EAAoBF,EAAO,EAAK,EAChCG,EAAcH,EAAO,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,EAEnCI,EAAkBC,EAAmB,EACrC,CAAE,4BAAAC,EAA6B,aAAAC,EAAc,gBAAAC,CAAgB,EAAIC,EAAY,EAC7E,CAAE,YAAAC,EAAa,qBAAAC,CAAqB,EAAIC,EAAoB,EAE5DC,EAAiBC,EAAkBpB,CAAO,EAE1CqB,EAAaC,GAAQ,IAAMtB,IAAYa,EAAc,CAACb,EAASa,CAAY,CAAC,EAE5EU,EAAyBC,EAC1BC,GAAsC,CACnCb,EAA4BZ,EAASyB,CAAO,CAChD,EACA,CAACzB,EAASY,CAA2B,CACzC,EAEMc,EAAqBF,EACtBG,GAA0C,CACvC,IAAMC,EAAeX,EAAqBjB,CAAO,EAC7C4B,EACIA,EAAaD,CAAU,GACvBX,EAAYhB,CAAO,EAGvBgB,EAAYhB,CAAO,CAE3B,EACA,CAACiB,EAAsBjB,EAASgB,CAAW,CAC/C,EAEMa,EAAsBL,EACvBM,GAAyB,CACtB,GAAI,CAAC7B,GAAiB,CAACkB,EAAgB,OAQvCO,EAN8C,CAC1C,OAAQ,SACR,YAAaI,EACb,QAAA9B,EACA,OAAQ,UACZ,CAC6B,CACjC,EACA,CAAC0B,EAAoBzB,EAAekB,EAAgBnB,CAAO,CAC/D,EAEA+B,GAAaF,CAAmB,EAEhC,IAAMG,EAAuBR,EACxBM,GAAmC,CAEhC,GADAhB,EAAgB,EACZ,CAACZ,EAAqB,OAE1B,IAAMyB,EAAwC,CAC1C,OAAQM,GAAoB,QAC5B,YAAaH,EACb,QAAA9B,EACA,OAAQ,UACZ,EAEA0B,EAAmBC,CAAU,CACjC,EACA,CAACD,EAAoBxB,EAAqBF,EAASc,CAAe,CACtE,EACAoB,GAAuB7B,EAAc2B,CAAoB,EAEzD,IAAMG,EAAQX,EAAY,IAAM,CAM5BE,EAL8C,CAC1C,OAAQ,SACR,QAAA1B,EACA,OAAQ,UACZ,CAC6B,CACjC,EAAG,CAAC0B,EAAoB1B,CAAO,CAAC,EAE1BoC,EAAUZ,EACZ,CAACa,EAAeC,IAAmB,CAC/B,IAAMC,EAAUhC,EAAS,QACzB,GAAI,CAACgC,EAAS,OAAO,KAGrB,IAAMC,EAAe,KAAK,IAAIH,EAAOlC,EAAQ,KAAK,EAC5CsC,EAAgB,KAAK,IAAIH,EAAQnC,EAAQ,MAAM,EAErD,OAAAoC,EAAQ,MAAM,MAAQ,GAAGC,CAAY,KACrCD,EAAQ,MAAM,OAAS,GAAGE,CAAa,KAEhC,CAAE,MAAOD,EAAc,OAAQC,CAAc,CACxD,EACA,CAACtC,EAAQ,OAAQA,EAAQ,KAAK,CAClC,EAEMuC,EAAoBlB,EAAY,IAAM,CACxC,IAAMmB,EAAOjC,EAAgB,KACvBkC,EAAOlC,EAAgB,IAEvBmC,EACFnC,EAAgB,OAASH,EAAS,SAAS,aAAe,GAAKX,EAC7DkD,EACFpC,EAAgB,QAAUH,EAAS,SAAS,cAAgB,GAAKX,EACrE,MAAO,CAAE,KAAA+C,EAAM,KAAAC,EAAM,KAAAC,EAAM,KAAAC,CAAK,CACpC,EAAG,CAACpC,CAAe,CAAC,EAEdqC,EAAcvB,EAChB,CAACwB,EAAWC,IAAc,CACtB,IAAMV,EAAUhC,EAAS,QACzB,GAAI,CAACgC,EAAS,OAAO,KAErB,IAAMW,EAASR,EAAkB,EAC3BS,EAAa,KAAK,IAAID,EAAO,KAAM,KAAK,IAAIF,EAAGE,EAAO,IAAI,CAAC,EAC3DE,EAAa,KAAK,IAAIF,EAAO,KAAM,KAAK,IAAID,EAAGC,EAAO,IAAI,CAAC,EAEjE,OAAAzC,EAAY,QAAU,CAAE,EAAG0C,EAAY,EAAGC,CAAW,EACrDb,EAAQ,MAAM,UAAY,aAAaY,CAAU,OAAOC,CAAU,MAE3D,CAAE,EAAGD,EAAY,EAAGC,CAAW,CAC1C,EACA,CAACV,CAAiB,CACtB,EAEA,OAAAW,GAAgB,IAAM,CAElB,GAAI9C,EAAS,SAAW,CAACC,EAAkB,QAAS,CAChD,GAAIJ,EACA2C,EAAY3C,EAAS,EAAGA,EAAS,CAAC,MAC/B,CACH,IAAMkD,EAAiB5C,EAAgB,MAAQA,EAAgB,KACzD6C,EAAkB7C,EAAgB,OAASA,EAAgB,IAC3DsC,EACFtC,EAAgB,KAAO4C,EAAiB,EAAI/C,EAAS,QAAQ,YAAc,EACzE,EACFG,EAAgB,IAAM6C,EAAkB,EAAIhD,EAAS,QAAQ,aAAe,EAChFwC,EAAYC,EAAG,CAAC,CACpB,CACAxC,EAAkB,QAAU,EAChC,CACJ,EAAG,CAACuC,EAAarC,EAAiBH,EAAUH,CAAQ,CAAC,EAErDoD,GAAU,IAAM,CACZ,GAAM,CAAE,EAAAR,EAAG,EAAAC,CAAE,EAAIxC,EAAY,QAC7BsC,EAAYC,EAAGC,CAAC,CACpB,EAAG,CAACF,EAAarC,CAAe,CAAC,EAG7Bf,GAAC8D,GAA6B,SAA7B,CACG,MAAO,CACH,aAAApD,EACA,SAAAE,EACA,QAAAJ,CACJ,EAEA,SAAAR,GAAC+D,EAAqB,SAArB,CACG,MAAO,CACH,MAAAvB,EACA,QAAAnC,EACA,eAAAmB,EACA,WAAAE,EACA,uBAAAE,EACA,QAAAa,EACA,YAAAW,CACJ,EAEA,SAAApD,GAAC,OAAI,IAAKU,EAAe,SAAAN,EAAS,EACtC,EACJ,CAER,EHxCW,cAAA4D,OAAA,oBAjJX,IAAMC,GAAmC,CACrC,KAAM,wBACN,MAAO,yBACP,IAAK,uBACL,OAAQ,0BACR,WAAY,4BACZ,YAAa,6BACb,cAAe,+BACf,eAAgB,+BACpB,EAEaC,EAAa,CAAC,CAAE,aAAAC,EAAc,KAAAC,EAAM,MAAAC,EAAO,IAAAC,EAAK,OAAAC,CAAO,IAAuB,CACvF,GAAM,CAAE,QAAAC,EAAS,YAAAC,CAAY,EAAIC,EAAiB,EAC5CC,EAAkBC,EAAmB,EACrCC,EAAcC,GAAO,EAAK,EAE1BC,EAAgBC,GAAaC,GAA6C,CAC5EA,EAAM,eAAe,EACrBA,EAAM,gBAAgB,EACtBJ,EAAY,QAAU,GACtB,SAAS,KAAK,MAAM,WAAa,MACrC,EAAG,CAAC,CAAC,EAECK,EAAcF,GAAY,IAAM,CAClCH,EAAY,QAAU,GACtB,SAAS,KAAK,MAAM,WAAa,EACrC,EAAG,CAAC,CAAC,EAECM,EAAgBH,GACjBC,GAAwB,CACrB,IAAMG,EAAUjB,EAAa,QAC7B,GAAI,CAACU,EAAY,SAAW,CAACO,EAAS,OAEtC,GAAM,CAAE,QAAAC,EAAS,QAAAC,CAAQ,EAAIL,EAEvBM,EAAOH,EAAQ,sBAAsB,EAGrCI,EAAW,KAAK,MAClB,KAAK,IACD,KAAK,IAAIH,EAASV,EAAgB,KAAOc,CAAoB,EAC7Dd,EAAgB,MAAQc,CAC5B,CACJ,EACMC,EAAW,KAAK,MAClB,KAAK,IACD,KAAK,IAAIJ,EAASX,EAAgB,IAAMc,CAAoB,EAC5Dd,EAAgB,OAASc,CAC7B,CACJ,EAEME,EAAW,KAAK,MAAMJ,EAAK,IAAI,EAC/BK,EAAY,KAAK,MAAML,EAAK,KAAK,EACjCM,EAAU,KAAK,MAAMN,EAAK,GAAG,EAC7BO,EAAa,KAAK,MAAMP,EAAK,MAAM,EAErCQ,EAAe,KAAK,MAAMR,EAAK,KAAK,EACpCS,EAAgB,KAAK,MAAMT,EAAK,MAAM,EACtCU,EAAaN,EACbO,EAAaL,EAGjB,GAAIxB,EAAO,CACP,IAAM8B,EAAWxB,EAAgB,MAAQgB,EACzCI,EAAe,KAAK,IAAIP,EAAWG,EAAUQ,CAAQ,CACzD,SAAW/B,EAAM,CACb,IAAMgC,EAAYZ,EACZa,EAAgB1B,EAAgB,KAChC2B,EAAW,KAAK,IAAIF,EAAWC,CAAa,EAC5CF,EAAWP,EAAYS,EAE7BN,EAAe,KAAK,IAAIH,EAAYU,EAAUH,CAAQ,EACtDF,EAAaK,CACjB,CAGA,GAAI/B,EAAQ,CACR,IAAMgC,EAAY5B,EAAgB,OAASkB,EAC3CG,EAAgB,KAAK,IAAIN,EAAWG,EAASU,CAAS,CAC1D,SAAWjC,EAAK,CACZ,IAAMkC,EAAYd,EACZe,EAAe9B,EAAgB,IAC/B+B,EAAW,KAAK,IAAIF,EAAWC,CAAY,EAC3CF,EAAYT,EAAaW,EAE/BT,EAAgB,KAAK,IAAIF,EAAaY,EAAUH,CAAS,EACzDL,EAAaQ,CACjB,CAEA,IAAMC,EAAanC,EAAQuB,EAAcC,CAAa,EACjDW,IAGDvC,GAAQuC,EAAW,MAAQZ,IAE3BE,EAAaL,EAAYe,EAAW,OAEpCrC,GAAOqC,EAAW,OAASX,IAE3BE,EAAaJ,EAAaa,EAAW,QAGzClC,EAAYwB,EAAYC,CAAU,EACtC,EACA,CAAC/B,EAAcE,EAAOD,EAAMG,EAAQD,EAAKE,EAASC,EAAaE,CAAe,CAClF,EACAiC,GAAU,IAAM,CACZ,IAAMxB,EAAUjB,EAAa,QAC7B,GAAI,CAACiB,EAAS,OACd,IAAMG,EAAOH,EAAQ,sBAAsB,EAErCyB,EAAiBlC,EAAgB,MAAQA,EAAgB,KACzDmC,EAAkBnC,EAAgB,OAASA,EAAgB,IAE7DoB,EAAeR,EAAK,MACpBS,EAAgBT,EAAK,OAGrBQ,EAAec,IACfd,EAAec,GAEfb,EAAgBc,IAChBd,EAAgBc,IAIhBf,IAAiBR,EAAK,OAASS,IAAkBT,EAAK,SACtDf,EAAQuB,EAAcC,CAAa,CAE3C,EAAG,CAACrB,EAAiBR,EAAcK,CAAO,CAAC,EAE3CoC,GAAU,KACN,OAAO,iBAAiB,cAAezB,EAAe,CAAE,QAAS,EAAK,CAAC,EACvE,OAAO,iBAAiB,YAAaD,CAAW,EACzC,IAAM,CACT,OAAO,oBAAoB,cAAeC,CAAa,EACvD,OAAO,oBAAoB,YAAaD,CAAW,CACvD,GACD,CAACC,EAAeD,CAAW,CAAC,EAE/B,IAAM6B,EAAO,CAACzC,GAAO,MAAOC,GAAU,SAAUH,GAAQ,OAAQC,GAAS,OAAO,EAC3E,OAAO,OAAO,EACd,KAAK,GAAG,EAEP2C,EAAY/C,GAAS8C,CAAI,EAC/B,OAAO/C,GAAC,OAAI,UAAWgD,EAAW,cAAejC,EAAe,CACpE,EOpKQ,mBAAAkC,GACI,OAAAC,EADJ,QAAAC,OAAA,oBAFD,IAAMC,GAAU,CAAC,CAAE,aAAAC,CAAa,IAE/BF,GAAAF,GAAA,CACI,UAAAC,EAACI,EAAA,CAAW,aAAcD,EAAc,KAAI,GAAC,EAC7CH,EAACI,EAAA,CAAW,aAAcD,EAAc,MAAK,GAAC,EAC9CH,EAACI,EAAA,CAAW,aAAcD,EAAc,IAAG,GAAC,EAC5CH,EAACI,EAAA,CAAW,aAAcD,EAAc,OAAM,GAAC,EAE/CH,EAACI,EAAA,CAAW,aAAcD,EAAc,IAAG,GAAC,KAAI,GAAC,EACjDH,EAACI,EAAA,CAAW,aAAcD,EAAc,IAAG,GAAC,MAAK,GAAC,EAClDH,EAACI,EAAA,CAAW,aAAcD,EAAc,OAAM,GAAC,KAAI,GAAC,EACpDH,EAACI,EAAA,CAAW,aAAcD,EAAc,OAAM,GAAC,MAAK,GAAC,GACzD,ETkGI,cAAAE,EAEI,QAAAC,OAFJ,oBAlGZ,IAAMC,GAAqB,CAAE,MAAO,IAAK,OAAQ,GAAI,EAExCC,GAAY,CAAC,CACtB,SAAAC,EACA,MAAAC,EACA,KAAAC,EAAOJ,EACX,IAAsC,CAClC,GAAM,CAAE,SAAAK,EAAU,QAAAC,CAAQ,EAAIC,GAAyB,EACjD,CAAE,QAAAC,EAAS,MAAAC,CAAM,EAAIC,EAAiB,EACtC,CAAE,gBAAAC,CAAgB,EAAIC,EAAY,EAElC,CAAE,WAAAC,EAAY,cAAAC,CAAc,EAAIC,GAAaV,CAAQ,EACrDW,EAAiBC,EAAkBT,CAAO,EAE1CU,EAAsB,KAAK,IAAId,EAAK,MAAOE,EAAQ,KAAK,EACxDa,EAAuB,KAAK,IAAIf,EAAK,OAAQE,EAAQ,MAAM,EAE3Dc,EAAuBC,GACzB,CAACC,EAA0CC,IAAyB,CAChED,EAAM,gBAAgB,EACjBN,GACDM,EAAM,eAAe,EAGzBX,EAAgBH,CAAO,EACnBe,GACAT,EAAcQ,CAAK,EAGvBjB,EAAS,SAAS,MAAM,CAC5B,EACA,CAACW,EAAgBR,EAASH,EAAUS,EAAeH,CAAe,CACtE,EAEA,OAAAa,GAAU,IAAM,CACZnB,EAAS,SAAS,MAAM,CAC5B,EAAG,CAACA,CAAQ,CAAC,EAEbmB,GAAU,IAAM,CACZ,GAAI,CAACR,EAAgB,OAErB,IAAMS,EAAUpB,EAAS,QACzB,GAAI,CAACoB,EAAS,OAEd,IAAMC,EAAaC,GAAqB,CAEpC,GAAIA,EAAE,MAAQ,MAAO,OAErB,IAAMC,EAAoBH,EAAQ,iBAC9B,2IACJ,EAEMI,EAAiB,MAAM,KAAKD,CAAiB,EAEnD,GAAIC,EAAe,SAAW,EAAG,CAC7BF,EAAE,eAAe,EACjB,MACJ,CAEA,IAAMG,EAAeD,EAAe,CAAC,EAC/BE,EAAcF,EAAeA,EAAe,OAAS,CAAC,EAGxDF,EAAE,UAAY,SAAS,gBAAkBG,GACzCH,EAAE,eAAe,EACjBI,EAAY,MAAM,GAGb,CAACJ,EAAE,UAAY,SAAS,gBAAkBI,IAC/CJ,EAAE,eAAe,EACjBG,EAAa,MAAM,EAE3B,EAEA,OAAAL,EAAQ,iBAAiB,UAAWC,CAAS,EACtC,IAAMD,EAAQ,oBAAoB,UAAWC,CAAS,CACjE,EAAG,CAACV,EAAgBX,CAAQ,CAAC,EAGzBN,GAAC,OACG,UAAWiC,GACP,iBACAhB,GAAkB,wBACtB,EACA,IAAKX,EACL,SAAU,GACV,KAAK,SAGL,aAAW,QACX,kBAAiBF,GAAS,KAAO,eAAeK,CAAO,GAAK,OAC5D,mBAAkB,iBAAiBA,CAAO,GAC1C,cAAgBmB,GAAMP,EAAqBO,EAAG,EAAI,EAClD,MAAO,CACH,MAAO,GAAGT,CAAmB,KAC7B,OAAQ,GAAGC,CAAoB,IACnC,EAEA,UAAArB,EAACmC,GAAA,CAAQ,aAAc5B,EAAU,EACjCN,GAAC,OAAI,UAAW,yBACZ,UAAAA,GAAC,OACG,UAAWiC,GACP,wBACAnB,GAAc,gCAClB,EACA,cAAgBc,GAAMP,EAAqBO,EAAG,EAAI,EAElD,UAAA7B,EAAC,OACG,UAAW,8BACX,GAAIK,GAAS,KAAO,eAAeK,CAAO,GAAK,OAE9C,SAAAL,EACL,EACAL,EAAC,UACG,KAAM,SACN,QAAS,IAAMW,EAAM,EACrB,cAAgBkB,GAAMA,EAAE,gBAAgB,EACxC,aAAW,cACX,UAAW,qCACd,gBAED,GACJ,EACA7B,EAAC,OACG,UAAW,sBACX,GAAI,iBAAiBU,CAAO,GAC5B,cAAgBmB,GAAMP,EAAqBO,EAAG,EAAK,EAElD,SAAAzB,EACL,GACJ,GACJ,CAER,ELrIA,IAAIgC,EAA8B,CAAC,EAC7BC,GAAY,IAAI,IAElBC,EAAgC,KAI9BC,EAAkB,IAAM,CAC1BF,GAAU,QAASG,GAAOA,EAAG,CAAC,CAClC,EAwCO,SAASC,GACZC,EACAC,EACAC,EACM,CACN,IAAMC,EAAU,OAAO,WAAW,EAE5B,CAAE,MAAAC,EAAO,KAAAC,EAAM,MAAAC,EAAO,GAAGC,CAAgB,EAAIN,GAAW,CAAC,EACzDO,EAAYF,GAAU,CAAC,EAG7B,GAAI,CADc,SAAS,eAAe,eAAe,EAErD,eAAQ,KACJ,8GAEJ,EACO,GAGX,IAAMG,EAAUC,GAAcC,GAAqB,CAC/C,QAAAR,EACA,GAAGI,EACH,SAAUG,GAAcE,GAAW,CAAE,MAAAR,EAAO,KAAAC,CAAK,EAAGK,GAAcV,EAAWQ,CAAS,CAAC,CAC3F,CAAC,EAED,OAAAd,EAAa,CACT,GAAGA,EACH,CAAE,QAASS,EAAS,QAASM,EAAS,aAAc,KAAM,YAAaP,CAAY,CACvF,EAEAN,EAAiBO,EACjBN,EAAgB,EACTM,CACX,CAEO,IAAMU,GAAeV,GAAoB,CAC5CT,EAAaA,EAAW,OAAQoB,GAAMA,EAAE,UAAYX,CAAO,EAEvDP,IAAmBO,IACnBP,EAAiB,MAGrBC,EAAgB,CACpB,EAEakB,GAAwBZ,GAAoB,CACrD,IAAMa,EAAQtB,EAAW,KAAMoB,GAAMA,EAAE,UAAYX,CAAO,EAC1D,OAAKa,EACEA,EAAM,aADM,IAEvB,EAWO,SAASC,GAA4Bd,EAAiBe,EAAmC,CAC5F,IAAMF,EAAQtB,EAAW,KAAMoB,GAAMA,EAAE,UAAYX,CAAO,EACtDa,IACAA,EAAM,aAAeE,EAE7B,CAQO,IAAMC,GAAchB,GAAoB,CAC3C,IAAMa,EAAQtB,EAAW,KAAMoB,GAAMA,EAAE,UAAYX,CAAO,EAC1D,GAAI,CAACa,EAAO,OAGZ,IAAMI,EAAwC,CAC1C,OAAQ,SACR,OAAQ,WACR,QAAAjB,CACJ,EAGIa,EAAM,cAEF,CADgBA,EAAM,aAAaI,CAAU,GAIrDP,GAAYV,CAAO,CACvB,EAOakB,GAAiB,IAAM,CAChC,IAAMC,EAAwB,CAAC,EAE/B,QAAWN,IAAS,CAAC,GAAGtB,CAAU,EAAG,CACjC,IAAM0B,EAAwC,CAC1C,OAAQ,SACR,OAAQ,WACR,QAASJ,EAAM,OACnB,EAEIA,EAAM,cAEF,CADgBA,EAAM,aAAaI,CAAU,GAIrDE,EAAY,KAAKN,EAAM,OAAO,CAClC,CAEA,GAAIM,EAAY,SAAW,EAAG,OAE9B,IAAMC,EAAc,IAAI,IAAID,CAAW,EACvC5B,EAAaA,EAAW,OAAQoB,GAAM,CAACS,EAAY,IAAIT,EAAE,OAAO,CAAC,EAE7DlB,GAAkB,MAAQ2B,EAAY,IAAI3B,CAAc,IACxDA,EAAiB,MAGrBC,EAAgB,CACpB,EAEa2B,GAAqBrB,GAAoB,CAClD,IAAMsB,EAAQ/B,EAAW,UAAWoB,GAAMA,EAAE,UAAYX,CAAO,EAC/D,GAAIsB,IAAU,GAAI,OAElB,IAAMT,EAAQtB,EAAW+B,CAAK,EAC9B/B,EAAa,CAAC,GAAGA,EAAW,OAAO,CAACgC,EAAGC,IAAMA,IAAMF,CAAK,EAAGT,CAAK,EAChEnB,EAAgB,CACpB,EAQa+B,GAAmBzB,GAAqB,CAC7CA,GAAW,KACXP,EAAiB,MAEjB4B,GAAkBrB,CAAO,EACzBP,EAAiBO,GAErBN,EAAgB,CACpB,EAEagC,GAAoB,IAAMjC,EAE1BkC,EAAqBC,IAC9BpC,GAAU,IAAIoC,CAAQ,EACf,IAAMpC,GAAU,OAAOoC,CAAQ,GAG7BC,GAAmB,IAAMtC,EACzBuC,GAAmB,IAAMrC,ED9N/B,IAAMsC,EAAkB,IAAM,CACjC,IAAMC,EAAaC,GAAqBC,EAAmBC,EAAgB,EACrEC,EAAiBH,GAAqBC,EAAmBG,EAAgB,EAE/E,MAAO,CAAE,WAAAL,EAAY,eAAAI,CAAe,CACxC,EDJO,IAAME,EAAsB,IAAM,CACrC,GAAM,CAAE,WAAAC,CAAW,EAAIC,EAAgB,EACvC,OAAOC,GACH,KAAO,CACH,WAAAF,EACA,YAAAG,GACA,qBAAAC,EACJ,GACA,CAACJ,CAAU,CACf,CACJ,EDkFY,OAIQ,YAAAK,GAEQ,OAAAC,GANhB,QAAAC,OAAA,oBA5EZ,SAASC,IAAiB,CACtB,GAAM,CAAC,CAAEC,CAAW,EAAIC,GAAYC,GAAMA,EAAI,EAAG,CAAC,EAClD,OAAOF,CACX,CAEA,IAAIG,GAAkB,GAETC,GAAW,CAAC,CAAE,SAAAC,CAAS,IAAgC,CAChE,IAAMC,EAASC,GAAWC,CAAe,EACnCC,EAAYH,IAAW,KAC7BI,GAAU,IAAM,CACZ,GAAI,CAACD,EACD,OAAIN,IACA,QAAQ,KACJ,2FAEJ,EAEJA,GAAkB,GAEX,IAAM,CACTA,GAAkB,EACtB,CAER,EAAG,CAACM,CAAS,CAAC,EAEd,IAAME,EAASL,IAAW,KACpBM,EAAaC,GAAQ,IAAM,OAAO,WAAW,EAAG,CAAC,CAAC,EAElD,CAAE,WAAAC,CAAW,EAAIC,EAAoB,EACrC,CAACC,EAAWC,CAAY,EAAIC,GAA6B,IAAI,EAC7DC,EAAqBC,GAAoC,IAAI,GAAK,EAElEpB,EAAcD,GAAe,EAEnCW,GAAU,IAAM,CACZ,GAAI,CAACC,EAAQ,OAEb,IAAMU,EAAM,SAAS,cAAc,KAAK,EACxC,OAAAA,EAAI,GAAK,gBACT,SAAS,KAAK,YAAYA,CAAG,EAC7BJ,EAAaI,CAAG,EAET,IAAM,CACT,SAAS,KAAK,YAAYA,CAAG,CACjC,CACJ,EAAG,CAACV,CAAM,CAAC,EASXW,GAAgB,IAAM,CACbX,GACLX,EAAY,CAChB,EAAG,CAACW,EAAQG,EAAW,OAAQd,CAAW,CAAC,EAE3C,IAAMuB,EAAgBZ,EAASK,EAAYV,EAAO,cAE5CkB,EAAcV,EAAW,OAC1BW,GAAOd,GAAUc,EAAE,aAAe,MAASA,EAAE,cAAgBb,CAClE,EAEMc,EAAoBC,GAAaC,GAC5BT,EAAmB,QAAQ,IAAIS,CAAO,GAAK,KACnD,CAAC,CAAC,EAEL,GAAIL,GAAiB,KAAM,OAAO,KAElC,IAAMM,EAAqBvB,GAAQ,mBAAqBoB,EAExD,OAAIf,EAEIb,GAACU,EAAgB,SAAhB,CACG,MAAO,CAAE,WAAAI,EAAY,OAAAN,EAAQ,cAAAiB,EAAe,kBAAAG,CAAkB,EAE7D,UAAAI,GACGjC,GAAAD,GAAA,CACK,SAAAkB,EAAW,IAAKW,GACb5B,GAAC,OAEG,IAAMkC,GAAO,CACLA,EACAZ,EAAmB,QAAQ,IAAIM,EAAE,QAASM,CAAE,EAE5CZ,EAAmB,QAAQ,OAAOM,EAAE,OAAO,CAEnD,EACA,gBAAeA,EAAE,SARZA,EAAE,OASX,CACH,EACL,EACAF,CACJ,EACCC,EAAY,IAAKC,GAAM,CACpB,IAAMO,EAAkBH,EAAmBJ,EAAE,OAAO,EACpD,OAAOO,GAAmBF,GAAaL,EAAE,QAASO,EAAiBP,EAAE,OAAO,CAChF,CAAC,EACApB,GACL,EAKJP,GAACU,EAAgB,SAAhB,CACG,MAAO,CAAE,WAAAI,EAAY,OAAAN,EAAQ,cAAAiB,EAAe,kBAAmBM,CAAmB,EAEjF,UAAAL,EAAY,IAAKC,GAAM,CACpB,IAAMO,EAAkBH,EAAmBJ,EAAE,OAAO,EACpD,OAAOO,GAAmBF,GAAaL,EAAE,QAASO,EAAiBP,EAAE,OAAO,CAChF,CAAC,EACApB,GACL,CAER","names":["styleInject","css","insertAt","head","style","styleInject","createContext","useContext","ModalyzeModalContext","useModalyzeModal","ctx","useCallback","useContext","useEffect","useLayoutEffect","useMemo","useReducer","useRef","useState","createPortal","useMemo","useSyncExternalStore","createElement","useCallback","useEffect","useRef","useRef","useState","useCallback","useDraggableState","ref","state","setState","setDragging","value","useDraggable","ref","isDragging","isDraggingRef","setDragging","useDraggableState","setPosition","useModalyzeModal","clickOffsetRef","useRef","onPointerDown","useCallback","event","rect","onPointerUp","onPointerMove","useEffect","useCallback","useContext","useMemo","createContext","ModalyzeContext","useModalyze","instanceId","useContext","ModalyzeContext","modalStack","focusedModalId","useModalyzeBase","createModal","useCallback","component","options","createModalInContainer","useMemo","m","closeModal","closeAllModals","setModalCloseRequestHandler","setFocusedModal","bringModalToFront","useCallback","useEffect","mergeClassNames","classes","useCallback","useEffect","useRef","useEffect","useState","getContainerBounds","useContainerBounds","containerBounds","setContainerBounds","updateBounds","createContext","useContext","modalyzeCloseReason","ModalyzeModalInternalContext","useModalyzeModalInternal","ctx","useCallback","useEffect","useLayoutEffect","useMemo","useRef","useEffect","useEscapeKey","onEscape","handleKeyDown","event","useEffect","useClickOutsideElement","ref","clickOutsideCallback","handleOutside","event","target","useSyncExternalStore","useIsModalFocused","modalId","useSyncExternalStore","subscribeToModals","getFocusedModalId","jsx","POSITION_EDGE_BUFFER","DEFAULT_MIN_SIZE","ModalContextWrapper","children","modalId","closeOnEscape","closeOnOutsideClick","minSize","position","containerRef","useRef","modalRef","hasInitialisedRef","positionRef","containerBounds","useContainerBounds","setModalCloseRequestHandler","frontModalId","setFocusedModal","useModalyze","removeModal","getModalCloseHandler","useModalyzeInternal","isFocusedModal","useIsModalFocused","isTopModal","useMemo","setCloseRequestHandler","useCallback","handler","handleCloseRequest","closeEvent","closeHandler","escapeCloseCallback","event","useEscapeKey","clickOutsideCallback","modalyzeCloseReason","useClickOutsideElement","close","setSize","width","height","element","clampedWidth","clampedHeight","getPositionLimits","minX","minY","maxX","maxY","setPosition","x","y","limits","correctedX","correctedY","useLayoutEffect","containerWidth","containerHeight","useEffect","ModalyzeModalInternalContext","ModalyzeModalContext","jsx","classMap","ResizerBar","containerRef","left","right","top","bottom","setSize","setPosition","useModalyzeModal","containerBounds","useContainerBounds","draggingRef","useRef","onPointerDown","useCallback","event","onPointerUp","onPointerMove","element","clientX","clientY","rect","roundedX","POSITION_EDGE_BUFFER","roundedY","rectLeft","rectRight","rectTop","rectBottom","updatedWidth","updatedHeight","translateX","translateY","maxWidth","proposedX","containerLeft","clampedX","maxHeight","proposedY","containerTop","clampedY","actualSize","useEffect","containerWidth","containerHeight","axis","className","Fragment","jsx","jsxs","Resizer","containerRef","ResizerBar","jsx","jsxs","DEFAULT_MODAL_SIZE","BaseModal","children","title","size","modalRef","minSize","useModalyzeModalInternal","modalId","close","useModalyzeModal","setFocusedModal","useModalyze","isDragging","onPointerDown","useDraggable","isFocusedModal","useIsModalFocused","clampedInitialWidth","clampedInitialHeight","onPointerDownHandler","useCallback","event","should_drag","useEffect","element","handleTab","e","focusableElements","focusableArray","firstElement","lastElement","mergeClassNames","Resizer","modalStack","listeners","focusedModalId","notifyListeners","fn","createModalInContainer","component","options","containerId","modalId","title","size","props","behaviourConfig","safeProps","element","createElement","ModalContextWrapper","BaseModal","removeModal","m","getModalCloseHandler","modal","setModalCloseRequestHandler","handler","closeModal","closeEvent","closeAllModals","closableIds","closableSet","bringModalToFront","index","_","i","setFocusedModal","getFocusedModalId","subscribeToModals","callback","getStackSnapshot","getFocusSnapshot","useModalyzeBase","modalStack","useSyncExternalStore","subscribeToModals","getStackSnapshot","focusedModalId","getFocusSnapshot","useModalyzeInternal","modalStack","useModalyzeBase","useMemo","removeModal","getModalCloseHandler","Fragment","jsx","jsxs","useForceUpdate","forceUpdate","useReducer","x","hasRootInstance","Modalyze","children","parent","useContext","ModalyzeContext","hasParent","useEffect","isRoot","instanceId","useMemo","modalStack","useModalyzeInternal","container","setContainer","useState","modalContainerRefs","useRef","div","useLayoutEffect","rootContainer","levelModals","m","getModalContainer","useCallback","modalId","parentGetContainer","createPortal","el","targetContainer"]}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "modalyze",
3
+ "version": "1.0.0",
4
+ "description": "React library for managing multiple draggable, resizable, and stackable modals with proper component scope preservation",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --dts --format esm,cjs --minify --sourcemap --clean --out-dir dist --inject-style",
19
+ "dev": "tsup src/index.ts --dts --format esm,cjs --out-dir dist --watch --sourcemap --inject-style",
20
+ "clean": "rm -rf dist",
21
+ "test": "vitest",
22
+ "test:ui": "vitest --ui",
23
+ "test:coverage": "vitest --coverage"
24
+ },
25
+ "keywords": [
26
+ "modal",
27
+ "react",
28
+ "modals",
29
+ "dialog",
30
+ "popup",
31
+ "draggable",
32
+ "resizable",
33
+ "stackable",
34
+ "window",
35
+ "windowing",
36
+ "scope-preservation",
37
+ "context",
38
+ "ui",
39
+ "component"
40
+ ],
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/kaundur/modalyze.git",
44
+ "directory": "packages/modalyze"
45
+ },
46
+ "homepage": "https://github.com/kaundur/modalyze",
47
+ "bugs": {
48
+ "url": "https://github.com/kaundur/modalyze/issues"
49
+ },
50
+ "sideEffects": [
51
+ "*.css"
52
+ ],
53
+ "author": "Kaundur",
54
+ "license": "MIT",
55
+ "peerDependencies": {
56
+ "react": ">=17",
57
+ "react-dom": ">=17"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "devDependencies": {
63
+ "@testing-library/jest-dom": "^6.9.1",
64
+ "@testing-library/react": "^16.3.1",
65
+ "@testing-library/user-event": "^14.6.1",
66
+ "@types/react": "^19.1.2",
67
+ "@types/react-dom": "^19.1.2",
68
+ "@vitejs/plugin-react": "^4.7.0",
69
+ "globals": "^16.1.0",
70
+ "jsdom": "^27.3.0",
71
+ "react": ">=17",
72
+ "react-dom": ">=17",
73
+ "tsup": "^8.5.0",
74
+ "typescript": "^5.8.3",
75
+ "vitest": "^4.0.16"
76
+ }
77
+ }