@inertiaui/modal-react 3.0.1 → 3.1.1
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.md +21 -0
- package/dist/ModalLink.d.ts +1 -1
- package/dist/inertiaui-modal.js +1602 -1818
- package/dist/inertiaui-modal.js.map +1 -1
- package/dist/inertiaui-modal.umd.cjs +1694 -1871
- package/dist/inertiaui-modal.umd.cjs.map +1 -1
- package/dist/inertiauiModal.d.ts +3 -3
- package/dist/types.d.ts +1 -1
- package/package.json +39 -65
- package/src/CloseButton.tsx +3 -19
- package/src/Deferred.tsx +1 -0
- package/src/HeadlessModal.tsx +151 -155
- package/src/Modal.tsx +132 -144
- package/src/ModalContent.tsx +13 -14
- package/src/ModalLink.tsx +4 -3
- package/src/ModalRenderer.tsx +1 -0
- package/src/ModalRoot.tsx +25 -31
- package/src/SlideoverContent.tsx +14 -15
- package/src/WhenVisible.tsx +2 -1
- package/src/cache.ts +4 -1
- package/src/config.ts +3 -4
- package/src/inertiauiModal.ts +11 -10
- package/src/types.ts +1 -1
package/src/Modal.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { lockScroll, markAriaHidden } from '@inertiaui/vanilla'
|
|
1
2
|
import { forwardRef, useRef, useImperativeHandle, useState, useEffect, useCallback, useMemo, ReactNode } from 'react'
|
|
2
3
|
import { createPortal } from 'react-dom'
|
|
4
|
+
|
|
5
|
+
import { getConfig } from './config'
|
|
3
6
|
import HeadlessModal, { HeadlessModalRef } from './HeadlessModal'
|
|
4
7
|
import ModalContent from './ModalContent'
|
|
5
8
|
import SlideoverContent from './SlideoverContent'
|
|
6
|
-
import { lockScroll, markAriaHidden } from '@inertiaui/vanilla'
|
|
7
|
-
import { getConfig } from './config'
|
|
8
9
|
import type { Modal as ModalType, ReloadOptions } from './types'
|
|
9
10
|
|
|
10
11
|
interface ModalConfig {
|
|
@@ -61,158 +62,145 @@ interface BackdropTransitionProps {
|
|
|
61
62
|
onAfterAppear?: () => void
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
const Modal = forwardRef<HeadlessModalRef, ModalProps>(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return children(contentProps)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return children
|
|
65
|
+
const Modal = forwardRef<HeadlessModalRef, ModalProps>((allProps, ref) => {
|
|
66
|
+
const { name, children, onFocus, onBlur, onClose, onSuccess, onAfterLeave, ...props } = allProps as ModalBaseProps & Record<string, unknown>
|
|
67
|
+
const renderChildren = (contentProps: ModalRenderProps) => {
|
|
68
|
+
if (typeof children === 'function') {
|
|
69
|
+
return children(contentProps)
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const cleanupAriaHiddenRef = useRef<(() => void) | null>(null)
|
|
78
|
-
const [rendered, setRendered] = useState(false)
|
|
79
|
-
const useNativeDialog = useMemo(() => getConfig('useNativeDialog') as boolean, [])
|
|
72
|
+
return children
|
|
73
|
+
}
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cleanupScrollLockRef.current?.()
|
|
87
|
-
cleanupAriaHiddenRef.current?.()
|
|
88
|
-
}
|
|
89
|
-
}, [])
|
|
75
|
+
const headlessModalRef = useRef<HeadlessModalRef>(null)
|
|
76
|
+
const cleanupScrollLockRef = useRef<(() => void) | null>(null)
|
|
77
|
+
const cleanupAriaHiddenRef = useRef<(() => void) | null>(null)
|
|
78
|
+
const [rendered, setRendered] = useState(false)
|
|
79
|
+
const useNativeDialog = useMemo(() => getConfig('useNativeDialog') as boolean, [])
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
onSuccess?.()
|
|
93
|
-
if (!cleanupScrollLockRef.current) {
|
|
94
|
-
cleanupScrollLockRef.current = lockScroll()
|
|
95
|
-
cleanupAriaHiddenRef.current = markAriaHidden(getConfig('appElement') as string)
|
|
96
|
-
}
|
|
97
|
-
}, [onSuccess])
|
|
81
|
+
useImperativeHandle(ref, () => headlessModalRef.current!, [headlessModalRef])
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
// Cleanup on unmount
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
return () => {
|
|
101
86
|
cleanupScrollLockRef.current?.()
|
|
102
87
|
cleanupAriaHiddenRef.current?.()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}, [onClose])
|
|
88
|
+
}
|
|
89
|
+
}, [])
|
|
106
90
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
const handleSuccess = useCallback(() => {
|
|
92
|
+
onSuccess?.()
|
|
93
|
+
if (!cleanupScrollLockRef.current) {
|
|
94
|
+
cleanupScrollLockRef.current = lockScroll()
|
|
95
|
+
cleanupAriaHiddenRef.current = markAriaHidden(getConfig('appElement') as string)
|
|
96
|
+
}
|
|
97
|
+
}, [onSuccess])
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
onSuccess={handleSuccess}
|
|
119
|
-
{...props}
|
|
120
|
-
>
|
|
121
|
-
{({
|
|
122
|
-
afterLeave,
|
|
123
|
-
close,
|
|
124
|
-
config,
|
|
125
|
-
emit,
|
|
126
|
-
getChildModal,
|
|
127
|
-
getParentModal,
|
|
128
|
-
id,
|
|
129
|
-
index,
|
|
130
|
-
isOpen,
|
|
131
|
-
modalContext,
|
|
132
|
-
onTopOfStack,
|
|
133
|
-
reload,
|
|
134
|
-
setOpen,
|
|
135
|
-
shouldRender,
|
|
136
|
-
...extraProps
|
|
137
|
-
}) => (
|
|
138
|
-
<ModalPortal>
|
|
139
|
-
<div
|
|
140
|
-
className="im-dialog relative z-20"
|
|
141
|
-
data-inertiaui-modal-id={id}
|
|
142
|
-
data-inertiaui-modal-index={index}
|
|
143
|
-
aria-hidden={!onTopOfStack}
|
|
144
|
-
>
|
|
145
|
-
{/* Only render backdrop for the first modal (non-native dialog mode) */}
|
|
146
|
-
{/* Native dialog uses ::backdrop pseudo-element instead */}
|
|
147
|
-
{index === 0 && !useNativeDialog && (
|
|
148
|
-
<BackdropTransition
|
|
149
|
-
show={isOpen}
|
|
150
|
-
appear={!rendered}
|
|
151
|
-
onAfterAppear={() => setRendered(true)}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
99
|
+
const handleClose = useCallback(() => {
|
|
100
|
+
onClose?.()
|
|
101
|
+
cleanupScrollLockRef.current?.()
|
|
102
|
+
cleanupAriaHiddenRef.current?.()
|
|
103
|
+
cleanupScrollLockRef.current = null
|
|
104
|
+
cleanupAriaHiddenRef.current = null
|
|
105
|
+
}, [onClose])
|
|
154
106
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
107
|
+
const handleAfterLeave = useCallback(() => {
|
|
108
|
+
onAfterLeave?.()
|
|
109
|
+
}, [onAfterLeave])
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<HeadlessModal
|
|
113
|
+
ref={headlessModalRef}
|
|
114
|
+
name={name}
|
|
115
|
+
onFocus={onFocus ?? undefined}
|
|
116
|
+
onBlur={onBlur ?? undefined}
|
|
117
|
+
onClose={handleClose}
|
|
118
|
+
onSuccess={handleSuccess}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
{({
|
|
122
|
+
afterLeave,
|
|
123
|
+
close,
|
|
124
|
+
config,
|
|
125
|
+
emit,
|
|
126
|
+
getChildModal,
|
|
127
|
+
getParentModal,
|
|
128
|
+
id,
|
|
129
|
+
index,
|
|
130
|
+
isOpen,
|
|
131
|
+
modalContext,
|
|
132
|
+
onTopOfStack,
|
|
133
|
+
reload,
|
|
134
|
+
setOpen,
|
|
135
|
+
shouldRender,
|
|
136
|
+
...extraProps
|
|
137
|
+
}) => (
|
|
138
|
+
<ModalPortal>
|
|
139
|
+
<div className="im-dialog relative z-20" data-inertiaui-modal-id={id} data-inertiaui-modal-index={index} aria-hidden={!onTopOfStack}>
|
|
140
|
+
{/* Only render backdrop for the first modal (non-native dialog mode) */}
|
|
141
|
+
{/* Native dialog uses ::backdrop pseudo-element instead */}
|
|
142
|
+
{index === 0 && !useNativeDialog && <BackdropTransition show={isOpen} appear={!rendered} onAfterAppear={() => setRendered(true)} />}
|
|
143
|
+
|
|
144
|
+
{/* The modal/slideover content itself */}
|
|
145
|
+
{config.slideover ? (
|
|
146
|
+
<SlideoverContent
|
|
147
|
+
modalContext={modalContext}
|
|
148
|
+
config={config}
|
|
149
|
+
useNativeDialog={useNativeDialog}
|
|
150
|
+
isFirstModal={index === 0}
|
|
151
|
+
onAfterLeave={handleAfterLeave}
|
|
152
|
+
>
|
|
153
|
+
{renderChildren({
|
|
154
|
+
...extraProps,
|
|
155
|
+
afterLeave,
|
|
156
|
+
close,
|
|
157
|
+
config,
|
|
158
|
+
emit,
|
|
159
|
+
getChildModal,
|
|
160
|
+
getParentModal,
|
|
161
|
+
id,
|
|
162
|
+
index,
|
|
163
|
+
isOpen,
|
|
164
|
+
modalContext,
|
|
165
|
+
onTopOfStack,
|
|
166
|
+
reload,
|
|
167
|
+
setOpen,
|
|
168
|
+
shouldRender,
|
|
169
|
+
})}
|
|
170
|
+
</SlideoverContent>
|
|
171
|
+
) : (
|
|
172
|
+
<ModalContent
|
|
173
|
+
modalContext={modalContext}
|
|
174
|
+
config={config}
|
|
175
|
+
useNativeDialog={useNativeDialog}
|
|
176
|
+
isFirstModal={index === 0}
|
|
177
|
+
onAfterLeave={handleAfterLeave}
|
|
178
|
+
>
|
|
179
|
+
{renderChildren({
|
|
180
|
+
...extraProps,
|
|
181
|
+
afterLeave,
|
|
182
|
+
close,
|
|
183
|
+
config,
|
|
184
|
+
emit,
|
|
185
|
+
getChildModal,
|
|
186
|
+
getParentModal,
|
|
187
|
+
id,
|
|
188
|
+
index,
|
|
189
|
+
isOpen,
|
|
190
|
+
modalContext,
|
|
191
|
+
onTopOfStack,
|
|
192
|
+
reload,
|
|
193
|
+
setOpen,
|
|
194
|
+
shouldRender,
|
|
195
|
+
})}
|
|
196
|
+
</ModalContent>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
</ModalPortal>
|
|
200
|
+
)}
|
|
201
|
+
</HeadlessModal>
|
|
202
|
+
)
|
|
203
|
+
})
|
|
216
204
|
|
|
217
205
|
// Simple portal component
|
|
218
206
|
function ModalPortal({ children }: { children: ReactNode }) {
|
package/src/ModalContent.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
2
|
+
import clsx from 'clsx'
|
|
1
3
|
import { useState, useEffect, useRef, useCallback, useMemo, ReactNode, SyntheticEvent, MouseEvent } from 'react'
|
|
4
|
+
|
|
2
5
|
import CloseButton from './CloseButton'
|
|
3
|
-
import clsx from 'clsx'
|
|
4
|
-
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
5
6
|
import { getMaxWidthClass } from './constants'
|
|
6
7
|
import type { Modal } from './types'
|
|
7
8
|
|
|
@@ -26,8 +27,8 @@ interface ModalContentProps {
|
|
|
26
27
|
|
|
27
28
|
const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onAfterLeave, children }: ModalContentProps) => {
|
|
28
29
|
const [isRendered, setIsRendered] = useState(false)
|
|
29
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
30
|
-
const [entered, setEntered] = useState(false)
|
|
30
|
+
const [isVisible, setIsVisible] = useState(false) // For backdrop sync
|
|
31
|
+
const [entered, setEntered] = useState(false) // After animation completes
|
|
31
32
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
32
33
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
33
34
|
const nativeWrapperRef = useRef<HTMLDivElement>(null)
|
|
@@ -230,12 +231,9 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
230
231
|
// ============ Render ============
|
|
231
232
|
|
|
232
233
|
const renderContent = () => (
|
|
233
|
-
<div
|
|
234
|
-
className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`}
|
|
235
|
-
data-inertiaui-modal-entered={entered}
|
|
236
|
-
>
|
|
234
|
+
<div className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`} data-inertiaui-modal-entered={entered}>
|
|
237
235
|
{config.closeButton && (
|
|
238
|
-
<div className="absolute
|
|
236
|
+
<div className="absolute top-0 right-0 pt-3 pr-3">
|
|
239
237
|
<CloseButton onClick={modalContext.close} />
|
|
240
238
|
</div>
|
|
241
239
|
)}
|
|
@@ -268,7 +266,11 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
268
266
|
>
|
|
269
267
|
<div
|
|
270
268
|
ref={nativeWrapperRef}
|
|
271
|
-
className={clsx(
|
|
269
|
+
className={clsx(
|
|
270
|
+
'im-modal-wrapper w-full transition-[filter] duration-300',
|
|
271
|
+
modalContext.onTopOfStack ? '' : 'blur-xs',
|
|
272
|
+
maxWidthClass,
|
|
273
|
+
)}
|
|
272
274
|
>
|
|
273
275
|
{renderContent()}
|
|
274
276
|
</div>
|
|
@@ -282,10 +284,7 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
282
284
|
if (!isRendered) return null
|
|
283
285
|
|
|
284
286
|
return (
|
|
285
|
-
<div
|
|
286
|
-
className="im-modal-container fixed inset-0 z-40 overflow-y-auto p-4"
|
|
287
|
-
onMouseDown={handleClickOutside}
|
|
288
|
-
>
|
|
287
|
+
<div className="im-modal-container fixed inset-0 z-40 overflow-y-auto p-4" onMouseDown={handleClickOutside}>
|
|
289
288
|
<div
|
|
290
289
|
className={clsx('im-modal-positioner flex min-h-full justify-center', {
|
|
291
290
|
'items-start': config.position === 'top',
|
package/src/ModalLink.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { RequestPayload } from '@inertiajs/core'
|
|
1
2
|
import { useCallback, useState, useEffect, useMemo, useRef, ReactNode, ElementType, MouseEvent } from 'react'
|
|
2
|
-
|
|
3
|
-
import { only, rejectNullValues, isStandardDomEvent } from './helpers'
|
|
3
|
+
|
|
4
4
|
import { getConfig } from './config'
|
|
5
|
+
import { only, rejectNullValues, isStandardDomEvent } from './helpers'
|
|
6
|
+
import { useModalStack, modalPropNames, prefetch as prefetchModal } from './ModalRoot'
|
|
5
7
|
import type { Modal, PrefetchOption, HttpMethod } from './types'
|
|
6
|
-
import type { RequestPayload } from '@inertiajs/core'
|
|
7
8
|
|
|
8
9
|
interface ModalLinkProps {
|
|
9
10
|
href: string
|
package/src/ModalRenderer.tsx
CHANGED
package/src/ModalRoot.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { createElement, useEffect, useLayoutEffect, useState, useRef, useReducer, ReactNode, ComponentType } from 'react'
|
|
2
|
-
import { except, kebabCase, generateId, sameUrlPath, parseResponseData } from './helpers'
|
|
3
|
-
import { ResponseCache } from './cache'
|
|
4
|
-
import { router, usePage, progress, http } from '@inertiajs/react'
|
|
5
1
|
import { mergeDataIntoQueryString, type RequestPayload, type HttpResponse, type HttpRequestConfig } from '@inertiajs/core'
|
|
2
|
+
import { router, usePage, progress, http } from '@inertiajs/react'
|
|
3
|
+
import { createElement, useEffect, useLayoutEffect, useState, useRef, useReducer, ReactNode, ComponentType } from 'react'
|
|
6
4
|
import { createContext, useContext } from 'react'
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
import { ResponseCache } from './cache'
|
|
8
7
|
import { getConfig } from './config'
|
|
8
|
+
import { except, kebabCase, generateId, sameUrlPath, parseResponseData } from './helpers'
|
|
9
|
+
import ModalRenderer from './ModalRenderer'
|
|
9
10
|
import type {
|
|
10
11
|
Modal,
|
|
11
12
|
ModalConfig,
|
|
@@ -346,7 +347,7 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
346
347
|
data: method === 'get' ? undefined : data,
|
|
347
348
|
params: method === 'get' ? data : undefined,
|
|
348
349
|
headers: {
|
|
349
|
-
...
|
|
350
|
+
...options.headers,
|
|
350
351
|
Accept: 'text/html, application/xhtml+xml',
|
|
351
352
|
'X-Inertia': 'true',
|
|
352
353
|
'X-Inertia-Partial-Component': this.response.component,
|
|
@@ -376,12 +377,7 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
376
377
|
}
|
|
377
378
|
|
|
378
379
|
const isValidModalResponse = (data: unknown): data is ModalResponseData => {
|
|
379
|
-
return (
|
|
380
|
-
typeof data === 'object' &&
|
|
381
|
-
data !== null &&
|
|
382
|
-
'component' in data &&
|
|
383
|
-
typeof (data as ModalResponseData).component === 'string'
|
|
384
|
-
)
|
|
380
|
+
return typeof data === 'object' && data !== null && 'component' in data && typeof (data as ModalResponseData).component === 'string'
|
|
385
381
|
}
|
|
386
382
|
|
|
387
383
|
const pushFromResponseData = (
|
|
@@ -399,9 +395,9 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
399
395
|
)
|
|
400
396
|
}
|
|
401
397
|
|
|
402
|
-
return router
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
return router
|
|
399
|
+
.resolveComponent(responseData.component)
|
|
400
|
+
.then((component) => push(component as ComponentType, responseData, config, onClose, onAfterLeave))
|
|
405
401
|
}
|
|
406
402
|
|
|
407
403
|
const loadDeferredProps = (modal: Modal) => {
|
|
@@ -665,10 +661,7 @@ export const renderApp = (App: ComponentType<{ children: (props: RenderInertiaAp
|
|
|
665
661
|
return Component.layout
|
|
666
662
|
.slice()
|
|
667
663
|
.reverse()
|
|
668
|
-
.reduce(
|
|
669
|
-
(acc, Layout) => createElement(Layout as ComponentType<Record<string, unknown>>, props, acc),
|
|
670
|
-
child as ReactNode,
|
|
671
|
-
)
|
|
664
|
+
.reduce((acc, Layout) => createElement(Layout as ComponentType<Record<string, unknown>>, props, acc), child as ReactNode)
|
|
672
665
|
}
|
|
673
666
|
|
|
674
667
|
return child
|
|
@@ -715,14 +708,18 @@ export const ModalRoot = ({ children }: ModalRootProps) => {
|
|
|
715
708
|
// Register interceptor in useLayoutEffect (fires during commit, before microtasks).
|
|
716
709
|
// Inertia 3 loads deferred props during page.set() microtasks which fire after commit
|
|
717
710
|
// but before useEffect — useLayoutEffect ensures the interceptor is registered in time.
|
|
718
|
-
useLayoutEffect(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
711
|
+
useLayoutEffect(
|
|
712
|
+
() =>
|
|
713
|
+
http.onRequest((config: HttpRequestConfig) => {
|
|
714
|
+
const baseUrlValue = baseUrl ?? pageRef.current.props._inertiaui_modal?.baseUrl ?? null
|
|
715
|
+
if (baseUrlValue) {
|
|
716
|
+
config.headers = config.headers ?? {}
|
|
717
|
+
config.headers['X-InertiaUI-Modal-Base-Url'] = baseUrlValue
|
|
718
|
+
}
|
|
719
|
+
return config
|
|
720
|
+
}),
|
|
721
|
+
[],
|
|
722
|
+
)
|
|
726
723
|
|
|
727
724
|
useEffect(() => router.on('start', () => (isNavigatingRef.current = true)), [])
|
|
728
725
|
useEffect(() => router.on('finish', () => (isNavigatingRef.current = false)), [])
|
|
@@ -831,9 +828,7 @@ export const ModalRoot = ({ children }: ModalRootProps) => {
|
|
|
831
828
|
// If there's no previous modal but we have modals in the stack (opened via XHR),
|
|
832
829
|
// check if the new modal matches any open modal and update its props
|
|
833
830
|
if (!previousModal && context && context.stack.length > 0) {
|
|
834
|
-
const existingModal = context.stack.find(
|
|
835
|
-
(m) => m.response?.component === newModal.component && sameUrlPath(m.response?.url, newModal.url),
|
|
836
|
-
)
|
|
831
|
+
const existingModal = context.stack.find((m) => m.response?.component === newModal.component && sameUrlPath(m.response?.url, newModal.url))
|
|
837
832
|
if (existingModal) {
|
|
838
833
|
existingModal.updateProps(newModal.props ?? {})
|
|
839
834
|
}
|
|
@@ -847,4 +842,3 @@ export const ModalRoot = ({ children }: ModalRootProps) => {
|
|
|
847
842
|
</>
|
|
848
843
|
)
|
|
849
844
|
}
|
|
850
|
-
|
package/src/SlideoverContent.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
2
|
+
import clsx from 'clsx'
|
|
1
3
|
import { useState, useEffect, useRef, useCallback, useMemo, ReactNode, SyntheticEvent, MouseEvent } from 'react'
|
|
4
|
+
|
|
2
5
|
import CloseButton from './CloseButton'
|
|
3
|
-
import clsx from 'clsx'
|
|
4
|
-
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
5
6
|
import { getMaxWidthClass } from './constants'
|
|
6
7
|
import type { Modal } from './types'
|
|
7
8
|
|
|
@@ -26,8 +27,8 @@ interface SlideoverContentProps {
|
|
|
26
27
|
|
|
27
28
|
const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal, onAfterLeave, children }: SlideoverContentProps) => {
|
|
28
29
|
const [isRendered, setIsRendered] = useState(false)
|
|
29
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
30
|
-
const [entered, setEntered] = useState(false)
|
|
30
|
+
const [isVisible, setIsVisible] = useState(false) // For backdrop sync
|
|
31
|
+
const [entered, setEntered] = useState(false) // After animation completes
|
|
31
32
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
32
33
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
33
34
|
const nativeWrapperRef = useRef<HTMLDivElement>(null)
|
|
@@ -240,12 +241,9 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
240
241
|
// ============ Render ============
|
|
241
242
|
|
|
242
243
|
const renderContent = () => (
|
|
243
|
-
<div
|
|
244
|
-
className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`}
|
|
245
|
-
data-inertiaui-modal-entered={entered}
|
|
246
|
-
>
|
|
244
|
+
<div className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`} data-inertiaui-modal-entered={entered}>
|
|
247
245
|
{config.closeButton && (
|
|
248
|
-
<div className="absolute
|
|
246
|
+
<div className="absolute top-0 right-0 pt-3 pr-3">
|
|
249
247
|
<CloseButton onClick={modalContext.close} />
|
|
250
248
|
</div>
|
|
251
249
|
)}
|
|
@@ -268,7 +266,7 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
268
266
|
onCancel={handleCancel}
|
|
269
267
|
onClick={handleDialogClick}
|
|
270
268
|
>
|
|
271
|
-
<div className="im-slideover-container fixed inset-0 overflow-
|
|
269
|
+
<div className="im-slideover-container fixed inset-0 overflow-x-hidden overflow-y-auto">
|
|
272
270
|
<div
|
|
273
271
|
className={clsx('im-slideover-positioner flex min-h-full items-center', {
|
|
274
272
|
'justify-start rtl:justify-end': config?.position === 'left',
|
|
@@ -277,7 +275,11 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
277
275
|
>
|
|
278
276
|
<div
|
|
279
277
|
ref={nativeWrapperRef}
|
|
280
|
-
className={clsx(
|
|
278
|
+
className={clsx(
|
|
279
|
+
'im-slideover-wrapper w-full transition-[filter] duration-300',
|
|
280
|
+
modalContext.onTopOfStack ? '' : 'blur-xs',
|
|
281
|
+
maxWidthClass,
|
|
282
|
+
)}
|
|
281
283
|
>
|
|
282
284
|
{renderContent()}
|
|
283
285
|
</div>
|
|
@@ -291,10 +293,7 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
291
293
|
if (!isRendered) return null
|
|
292
294
|
|
|
293
295
|
return (
|
|
294
|
-
<div
|
|
295
|
-
className="im-slideover-container fixed inset-0 z-40 overflow-y-auto overflow-x-hidden"
|
|
296
|
-
onMouseDown={handleClickOutside}
|
|
297
|
-
>
|
|
296
|
+
<div className="im-slideover-container fixed inset-0 z-40 overflow-x-hidden overflow-y-auto" onMouseDown={handleClickOutside}>
|
|
298
297
|
<div
|
|
299
298
|
className={clsx('im-slideover-positioner flex min-h-full items-center', {
|
|
300
299
|
'justify-start rtl:justify-end': config?.position === 'left',
|
package/src/WhenVisible.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// See: https://github.com/inertiajs/inertia/blob/48bcd21fb7daf467d0df1bfde2408f161f94a579/packages/react/src/WhenVisible.ts
|
|
2
2
|
import { createElement, useCallback, useEffect, useRef, useState, ReactNode, ElementType } from 'react'
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import type { ReloadOptions } from './types'
|
|
5
|
+
import useModal from './useModal'
|
|
5
6
|
|
|
6
7
|
interface WhenVisibleProps {
|
|
7
8
|
children: ReactNode
|
package/src/cache.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -61,8 +61,8 @@ class Config {
|
|
|
61
61
|
navigate: key.navigate ?? defaultConfig.navigate,
|
|
62
62
|
useNativeDialog: key.useNativeDialog ?? defaultConfig.useNativeDialog,
|
|
63
63
|
appElement: key.appElement !== undefined ? key.appElement : defaultConfig.appElement,
|
|
64
|
-
modal: { ...defaultConfig.modal, ...
|
|
65
|
-
slideover: { ...defaultConfig.slideover, ...
|
|
64
|
+
modal: { ...defaultConfig.modal, ...key.modal },
|
|
65
|
+
slideover: { ...defaultConfig.slideover, ...key.slideover },
|
|
66
66
|
}
|
|
67
67
|
return
|
|
68
68
|
}
|
|
@@ -95,5 +95,4 @@ const configInstance = new Config()
|
|
|
95
95
|
export const resetConfig = (): void => configInstance.reset()
|
|
96
96
|
export const putConfig = (key: string | Partial<ModalConfig>, value?: unknown): void => configInstance.put(key, value)
|
|
97
97
|
export const getConfig = (key?: string): unknown => configInstance.get(key)
|
|
98
|
-
export const getConfigByType = (isSlideover: boolean, key: string): unknown =>
|
|
99
|
-
configInstance.get(isSlideover ? `slideover.${key}` : `modal.${key}`)
|
|
98
|
+
export const getConfigByType = (isSlideover: boolean, key: string): unknown => configInstance.get(isSlideover ? `slideover.${key}` : `modal.${key}`)
|
package/src/inertiauiModal.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import * as dialogUtils from '@inertiaui/vanilla'
|
|
1
2
|
import { createElement } from 'react'
|
|
3
|
+
|
|
2
4
|
import { getConfig, putConfig, resetConfig } from './config'
|
|
3
|
-
import { useModalIndex } from './ModalRenderer'
|
|
4
|
-
import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot'
|
|
5
|
-
import useModal from './useModal'
|
|
6
5
|
import Deferred from './Deferred'
|
|
7
6
|
import HeadlessModal from './HeadlessModal'
|
|
8
7
|
import Modal from './Modal'
|
|
9
8
|
import ModalLink from './ModalLink'
|
|
9
|
+
import { useModalIndex } from './ModalRenderer'
|
|
10
|
+
import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot'
|
|
11
|
+
import useModal from './useModal'
|
|
10
12
|
import WhenVisible from './WhenVisible'
|
|
11
|
-
import * as dialogUtils from '@inertiaui/vanilla'
|
|
12
13
|
|
|
13
14
|
// Types
|
|
14
15
|
export type {
|
|
@@ -34,12 +35,12 @@ export type { ModalTypeConfig } from './config'
|
|
|
34
35
|
|
|
35
36
|
export type { CleanupFunction, FocusTrapOptions, EscapeKeyOptions } from '@inertiaui/vanilla'
|
|
36
37
|
|
|
37
|
-
const setPageLayout =
|
|
38
|
-
layout: React.ComponentType<{ children?: React.ReactNode }
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
38
|
+
const setPageLayout =
|
|
39
|
+
<T extends { default: { layout?: (page: React.ReactNode) => React.ReactNode } }>(layout: React.ComponentType<{ children?: React.ReactNode }>) =>
|
|
40
|
+
(module: T): T => {
|
|
41
|
+
module.default.layout = (page) => createElement(layout, { children: page })
|
|
42
|
+
return module
|
|
43
|
+
}
|
|
43
44
|
|
|
44
45
|
export {
|
|
45
46
|
Deferred,
|