@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.
@@ -1,11 +1,11 @@
1
1
  import { getConfig, putConfig, resetConfig } from './config';
2
- import { useModalIndex } from './ModalRenderer';
3
- import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot';
4
- import { default as useModal } from './useModal';
5
2
  import { default as Deferred } from './Deferred';
6
3
  import { default as HeadlessModal } from './HeadlessModal';
7
4
  import { default as Modal } from './Modal';
8
5
  import { default as ModalLink } from './ModalLink';
6
+ import { useModalIndex } from './ModalRenderer';
7
+ import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot';
8
+ import { default as useModal } from './useModal';
9
9
  import { default as WhenVisible } from './WhenVisible';
10
10
  import * as dialogUtils from '@inertiaui/vanilla';
11
11
  export type { Modal as ModalInstance, ModalConfig, ModalResponseData, ModalStackContextValue, VisitOptions, ReloadOptions, EventCallback, ComponentResolver, HttpMethod, PageProps, ModalRootProps, ModalRendererProps, LocalModal, PrefetchOption, PrefetchOptions, } from './types';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ComponentType, ReactNode } from 'react';
2
1
  import { RequestPayload, HttpResponse, Method } from '@inertiajs/core';
2
+ import { ComponentType, ReactNode } from 'react';
3
3
  import { ModalTypeConfig } from './config';
4
4
  export type HttpMethod = Method;
5
5
  export interface ModalResponseData {
package/package.json CHANGED
@@ -1,67 +1,41 @@
1
1
  {
2
- "name": "@inertiaui/modal-react",
3
- "author": "Pascal Baljet <pascal@protone.media>",
4
- "version": "3.0.1",
5
- "private": false,
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/inertiaui/modal.git",
10
- "directory": "react"
11
- },
12
- "type": "module",
13
- "files": [
14
- "dist",
15
- "src"
16
- ],
17
- "module": "./dist/inertiaui-modal.js",
18
- "main": "./dist/inertiaui-modal.umd.cjs",
19
- "types": "./dist/inertiaui-modal.d.ts",
20
- "exports": {
21
- ".": {
22
- "types": "./dist/inertiaui-modal.d.ts",
23
- "import": "./dist/inertiaui-modal.js",
24
- "require": "./dist/inertiaui-modal.umd.cjs"
25
- }
26
- },
27
- "scripts": {
28
- "dev": "NODE_ENV=development vite build --watch",
29
- "build": "vite build",
30
- "build-watch": "NODE_ENV=development vite build --watch",
31
- "lint": "eslint src/",
32
- "eslint": "eslint src/",
33
- "prettier": "prettier -w src/",
34
- "test": "vitest"
35
- },
36
- "devDependencies": {
37
- "@heroicons/react": "^2.1.4",
38
- "@inertiajs/react": "^3.0.0",
39
- "@inertiaui/vanilla": "^0.3.0",
40
- "@testing-library/react": "^16.0.0",
41
- "@types/react": "^19.0.0",
42
- "@types/react-dom": "^19.0.0",
43
- "@typescript-eslint/eslint-plugin": "^8.0.0",
44
- "@typescript-eslint/parser": "^8.0.0",
45
- "@vitejs/plugin-react": "^4.3.1",
46
- "@vitest/coverage-v8": "^4.0.0",
47
- "@vitest/ui": "^4.0.0",
48
- "clsx": "^2.1.1",
49
- "eslint": "^9.0.0",
50
- "happy-dom": "^20.0.0",
51
- "prettier": "^3.2.4",
52
- "react": "^19.0.0",
53
- "react-dom": "^19.0.0",
54
- "typescript": "^5.7.0",
55
- "vite": "^7.3.1",
56
- "vite-plugin-dts": "^4.5.0",
57
- "vitest": "^4.0.0"
58
- },
59
- "dependencies": {
60
- "@inertiaui/vanilla": "^0.3.0"
61
- },
62
- "peerDependencies": {
63
- "@inertiajs/react": "^3.0.0",
64
- "react": "^19.0.0",
65
- "react-dom": "^19.0.0"
2
+ "name": "@inertiaui/modal-react",
3
+ "author": "Pascal Baljet <pascal@protone.media>",
4
+ "version": "3.1.1",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/inertiaui/modal.git",
10
+ "directory": "react"
11
+ },
12
+ "type": "module",
13
+ "files": [
14
+ "dist",
15
+ "src"
16
+ ],
17
+ "module": "./dist/inertiaui-modal.js",
18
+ "main": "./dist/inertiaui-modal.umd.cjs",
19
+ "types": "./dist/inertiaui-modal.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/inertiaui-modal.d.ts",
23
+ "import": "./dist/inertiaui-modal.js",
24
+ "require": "./dist/inertiaui-modal.umd.cjs"
66
25
  }
67
- }
26
+ },
27
+ "dependencies": {
28
+ "@inertiaui/vanilla": "^1.0.2"
29
+ },
30
+ "peerDependencies": {
31
+ "@inertiajs/react": "^3.0.0",
32
+ "react": "^19.0.0",
33
+ "react-dom": "^19.0.0"
34
+ },
35
+ "scripts": {
36
+ "dev": "NODE_ENV=development vite build --watch",
37
+ "build": "vite build",
38
+ "build-watch": "NODE_ENV=development vite build --watch",
39
+ "test": "vitest"
40
+ }
41
+ }
@@ -4,26 +4,10 @@ interface CloseButtonProps {
4
4
 
5
5
  export default function CloseButton({ onClick }: CloseButtonProps) {
6
6
  return (
7
- <button
8
- type="button"
9
- className="im-close-button text-gray-400 hover:text-gray-500"
10
- onClick={onClick}
11
- >
7
+ <button type="button" className="im-close-button text-gray-400 hover:text-gray-500" onClick={onClick}>
12
8
  <span className="sr-only">Close</span>
13
- <svg
14
- className="size-6"
15
- xmlns="http://www.w3.org/2000/svg"
16
- fill="none"
17
- viewBox="0 0 24 24"
18
- strokeWidth="2"
19
- stroke="currentColor"
20
- aria-hidden="true"
21
- >
22
- <path
23
- strokeLinecap="round"
24
- strokeLinejoin="round"
25
- d="M6 18L18 6M6 6l12 12"
26
- />
9
+ <svg className="size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor" aria-hidden="true">
10
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
27
11
  </svg>
28
12
  </button>
29
13
  )
package/src/Deferred.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  // See: https://github.com/inertiajs/inertia/blob/48bcd21fb7daf467d0df1bfde2408f161f94a579/packages/react/src/Deferred.ts
2
2
  import { useEffect, useState, ReactNode } from 'react'
3
+
3
4
  import useModal from './useModal'
4
5
 
5
6
  interface DeferredProps {
@@ -1,8 +1,9 @@
1
1
  import { useMemo, useState, forwardRef, useImperativeHandle, useEffect, useRef, ReactNode } from 'react'
2
+
2
3
  import { getConfig, getConfigByType } from './config'
3
4
  import { useModalIndex } from './ModalRenderer'
4
- import { useModalStack } from './ModalRoot'
5
5
  import ModalRenderer from './ModalRenderer'
6
+ import { useModalStack } from './ModalRoot'
6
7
  import type { Modal, ModalConfig, ReloadOptions } from './types'
7
8
 
8
9
  interface HeadlessModalConfig {
@@ -71,168 +72,163 @@ export interface HeadlessModalRef {
71
72
  readonly shouldRender: boolean | undefined
72
73
  }
73
74
 
74
- const HeadlessModal = forwardRef<HeadlessModalRef, HeadlessModalProps>(
75
- (allProps, ref) => {
76
- const { name, children, onFocus, onBlur, onClose, onSuccess, ...props } = allProps as HeadlessModalBaseProps & Record<string, unknown>
77
- const modalIndex = useModalIndex()
78
- const { stack, registerLocalModal, removeLocalModal } = useModalStack()
79
-
80
- const [localModalContext, setLocalModalContext] = useState<Modal | null>(null)
81
- const modalContext = useMemo(
82
- () => (name ? localModalContext : stack[modalIndex]),
83
- [name, localModalContext, modalIndex, stack],
84
- )
85
-
86
- const nextIndex = useMemo(() => {
87
- return stack.find((m) => m.shouldRender && m.index > (modalContext?.index ?? -1))?.index
88
- }, [modalIndex, stack])
89
-
90
- const configSlideover = useMemo(
91
- () => modalContext?.config.slideover ?? props.slideover ?? getConfig('type') === 'slideover',
92
- [props.slideover, modalContext?.config.slideover],
93
- )
94
-
95
- const config: HeadlessModalConfig = useMemo(
96
- () => ({
97
- slideover: configSlideover as boolean,
98
- closeButton: (props.closeButton ?? getConfigByType(configSlideover as boolean, 'closeButton')) as boolean,
99
- closeExplicitly: (props.closeExplicitly ?? getConfigByType(configSlideover as boolean, 'closeExplicitly')) as boolean,
100
- closeOnClickOutside: (props.closeOnClickOutside ?? getConfigByType(configSlideover as boolean, 'closeOnClickOutside')) as boolean,
101
- maxWidth: (props.maxWidth ?? getConfigByType(configSlideover as boolean, 'maxWidth')) as string,
102
- paddingClasses: (props.paddingClasses ?? getConfigByType(configSlideover as boolean, 'paddingClasses')) as string,
103
- panelClasses: (props.panelClasses ?? getConfigByType(configSlideover as boolean, 'panelClasses')) as string,
104
- position: (props.position ?? getConfigByType(configSlideover as boolean, 'position')) as string,
105
- ...modalContext?.config,
106
- }),
107
- [props, modalContext?.config, configSlideover],
108
- )
109
-
110
- useEffect(() => {
111
- if (name) {
112
- let removeListeners: (() => void) | null = null
113
-
114
- registerLocalModal(name as string, (localContext) => {
115
- removeListeners = localContext.registerEventListenersFromProps(props as Record<string, unknown>)
116
- setLocalModalContext(localContext)
117
- })
118
-
119
- return () => {
120
- removeListeners?.()
121
- removeListeners = null
122
- removeLocalModal(name as string)
123
- }
75
+ const HeadlessModal = forwardRef<HeadlessModalRef, HeadlessModalProps>((allProps, ref) => {
76
+ const { name, children, onFocus, onBlur, onClose, onSuccess, ...props } = allProps as HeadlessModalBaseProps & Record<string, unknown>
77
+ const modalIndex = useModalIndex()
78
+ const { stack, registerLocalModal, removeLocalModal } = useModalStack()
79
+
80
+ const [localModalContext, setLocalModalContext] = useState<Modal | null>(null)
81
+ const modalContext = useMemo(() => (name ? localModalContext : stack[modalIndex]), [name, localModalContext, modalIndex, stack])
82
+
83
+ const nextIndex = useMemo(() => {
84
+ return stack.find((m) => m.shouldRender && m.index > (modalContext?.index ?? -1))?.index
85
+ }, [modalIndex, stack])
86
+
87
+ const configSlideover = useMemo(
88
+ () => modalContext?.config.slideover ?? props.slideover ?? getConfig('type') === 'slideover',
89
+ [props.slideover, modalContext?.config.slideover],
90
+ )
91
+
92
+ const config: HeadlessModalConfig = useMemo(
93
+ () => ({
94
+ slideover: configSlideover as boolean,
95
+ closeButton: (props.closeButton ?? getConfigByType(configSlideover as boolean, 'closeButton')) as boolean,
96
+ closeExplicitly: (props.closeExplicitly ?? getConfigByType(configSlideover as boolean, 'closeExplicitly')) as boolean,
97
+ closeOnClickOutside: (props.closeOnClickOutside ?? getConfigByType(configSlideover as boolean, 'closeOnClickOutside')) as boolean,
98
+ maxWidth: (props.maxWidth ?? getConfigByType(configSlideover as boolean, 'maxWidth')) as string,
99
+ paddingClasses: (props.paddingClasses ?? getConfigByType(configSlideover as boolean, 'paddingClasses')) as string,
100
+ panelClasses: (props.panelClasses ?? getConfigByType(configSlideover as boolean, 'panelClasses')) as string,
101
+ position: (props.position ?? getConfigByType(configSlideover as boolean, 'position')) as string,
102
+ ...modalContext?.config,
103
+ }),
104
+ [props, modalContext?.config, configSlideover],
105
+ )
106
+
107
+ useEffect(() => {
108
+ if (name) {
109
+ let removeListeners: (() => void) | null = null
110
+
111
+ registerLocalModal(name as string, (localContext) => {
112
+ removeListeners = localContext.registerEventListenersFromProps(props as Record<string, unknown>)
113
+ setLocalModalContext(localContext)
114
+ })
115
+
116
+ return () => {
117
+ removeListeners?.()
118
+ removeListeners = null
119
+ removeLocalModal(name as string)
124
120
  }
121
+ }
125
122
 
126
- return modalContext?.registerEventListenersFromProps(props as Record<string, unknown>)
127
- }, [name])
128
-
129
- // Store the latest modalContext in a ref to maintain reference
130
- const modalContextRef = useRef(modalContext)
131
-
132
- // Update the ref whenever modalContext changes
133
- useEffect(() => {
134
- modalContextRef.current = modalContext
135
- }, [modalContext])
136
-
137
- // Track previous isOpen value to only emit close when transitioning from true to false
138
- const previousIsOpenRef = useRef<boolean | undefined>(undefined)
139
-
140
- useEffect(() => {
141
- if (modalContext != null) {
142
- if (modalContext.isOpen) {
143
- onSuccess?.()
144
- } else if (previousIsOpenRef.current === true) {
145
- // Only call onClose when transitioning from open to closed,
146
- // not when the component first mounts with isOpen undefined/false
147
- onClose?.()
148
- }
149
- previousIsOpenRef.current = modalContext.isOpen
150
- }
151
- }, [modalContext?.isOpen])
123
+ return modalContext?.registerEventListenersFromProps(props as Record<string, unknown>)
124
+ }, [name])
152
125
 
153
- const [rendered, setRendered] = useState(false)
126
+ // Store the latest modalContext in a ref to maintain reference
127
+ const modalContextRef = useRef(modalContext)
154
128
 
155
- useEffect(() => {
156
- if (rendered && modalContext != null && modalContext.isOpen) {
157
- if (modalContext.onTopOfStack) {
158
- onFocus?.()
159
- } else {
160
- onBlur?.()
161
- }
129
+ // Update the ref whenever modalContext changes
130
+ useEffect(() => {
131
+ modalContextRef.current = modalContext
132
+ }, [modalContext])
133
+
134
+ // Track previous isOpen value to only emit close when transitioning from true to false
135
+ const previousIsOpenRef = useRef<boolean | undefined>(undefined)
136
+
137
+ useEffect(() => {
138
+ if (modalContext != null) {
139
+ if (modalContext.isOpen) {
140
+ onSuccess?.()
141
+ } else if (previousIsOpenRef.current === true) {
142
+ // Only call onClose when transitioning from open to closed,
143
+ // not when the component first mounts with isOpen undefined/false
144
+ onClose?.()
162
145
  }
146
+ previousIsOpenRef.current = modalContext.isOpen
147
+ }
148
+ }, [modalContext?.isOpen])
163
149
 
164
- setRendered(true)
165
- }, [modalContext?.onTopOfStack])
166
-
167
- useImperativeHandle(
168
- ref,
169
- () => ({
170
- afterLeave: () => modalContextRef.current?.afterLeave(),
171
- close: () => modalContextRef.current?.close(),
172
- emit: (...args: [string, ...unknown[]]) => modalContextRef.current?.emit(...args),
173
- getChildModal: () => modalContextRef.current?.getChildModal(),
174
- getParentModal: () => modalContextRef.current?.getParentModal(),
175
- reload: (options?: ReloadOptions) => modalContextRef.current?.reload(options),
176
- setOpen: (open: boolean) => modalContextRef.current?.setOpen(open),
177
-
178
- get id() {
179
- return modalContextRef.current?.id
180
- },
181
- get index() {
182
- return modalContextRef.current?.index
183
- },
184
- get isOpen() {
185
- return modalContextRef.current?.isOpen
186
- },
187
- get config() {
188
- return modalContextRef.current?.config
189
- },
190
- get modalContext() {
191
- return modalContextRef.current
192
- },
193
- get onTopOfStack() {
194
- return modalContextRef.current?.onTopOfStack
195
- },
196
- get shouldRender() {
197
- return modalContextRef.current?.shouldRender
198
- },
199
- }),
200
- [modalContext],
201
- )
202
-
203
- if (!modalContext?.shouldRender) {
204
- return null
150
+ const [rendered, setRendered] = useState(false)
151
+
152
+ useEffect(() => {
153
+ if (rendered && modalContext != null && modalContext.isOpen) {
154
+ if (modalContext.onTopOfStack) {
155
+ onFocus?.()
156
+ } else {
157
+ onBlur?.()
158
+ }
205
159
  }
206
160
 
207
- return (
208
- <>
209
- {typeof children === 'function'
210
- ? children({
211
- // Spread props first so they can be overridden by built-in props
212
- ...modalContext.props,
213
- afterLeave: modalContext.afterLeave,
214
- close: modalContext.close,
215
- config,
216
- emit: modalContext.emit,
217
- getChildModal: modalContext.getChildModal,
218
- getParentModal: modalContext.getParentModal,
219
- id: modalContext.id,
220
- index: modalContext.index,
221
- isOpen: modalContext.isOpen,
222
- modalContext,
223
- onTopOfStack: modalContext.onTopOfStack,
224
- reload: modalContext.reload,
225
- setOpen: modalContext.setOpen,
226
- shouldRender: modalContext.shouldRender,
227
- })
228
- : children}
229
-
230
- {/* Next modal in the stack */}
231
- {nextIndex !== undefined && <ModalRenderer index={nextIndex} />}
232
- </>
233
- )
234
- },
235
- )
161
+ setRendered(true)
162
+ }, [modalContext?.onTopOfStack])
163
+
164
+ useImperativeHandle(
165
+ ref,
166
+ () => ({
167
+ afterLeave: () => modalContextRef.current?.afterLeave(),
168
+ close: () => modalContextRef.current?.close(),
169
+ emit: (...args: [string, ...unknown[]]) => modalContextRef.current?.emit(...args),
170
+ getChildModal: () => modalContextRef.current?.getChildModal(),
171
+ getParentModal: () => modalContextRef.current?.getParentModal(),
172
+ reload: (options?: ReloadOptions) => modalContextRef.current?.reload(options),
173
+ setOpen: (open: boolean) => modalContextRef.current?.setOpen(open),
174
+
175
+ get id() {
176
+ return modalContextRef.current?.id
177
+ },
178
+ get index() {
179
+ return modalContextRef.current?.index
180
+ },
181
+ get isOpen() {
182
+ return modalContextRef.current?.isOpen
183
+ },
184
+ get config() {
185
+ return modalContextRef.current?.config
186
+ },
187
+ get modalContext() {
188
+ return modalContextRef.current
189
+ },
190
+ get onTopOfStack() {
191
+ return modalContextRef.current?.onTopOfStack
192
+ },
193
+ get shouldRender() {
194
+ return modalContextRef.current?.shouldRender
195
+ },
196
+ }),
197
+ [modalContext],
198
+ )
199
+
200
+ if (!modalContext?.shouldRender) {
201
+ return null
202
+ }
203
+
204
+ return (
205
+ <>
206
+ {typeof children === 'function'
207
+ ? children({
208
+ // Spread props first so they can be overridden by built-in props
209
+ ...modalContext.props,
210
+ afterLeave: modalContext.afterLeave,
211
+ close: modalContext.close,
212
+ config,
213
+ emit: modalContext.emit,
214
+ getChildModal: modalContext.getChildModal,
215
+ getParentModal: modalContext.getParentModal,
216
+ id: modalContext.id,
217
+ index: modalContext.index,
218
+ isOpen: modalContext.isOpen,
219
+ modalContext,
220
+ onTopOfStack: modalContext.onTopOfStack,
221
+ reload: modalContext.reload,
222
+ setOpen: modalContext.setOpen,
223
+ shouldRender: modalContext.shouldRender,
224
+ })
225
+ : children}
226
+
227
+ {/* Next modal in the stack */}
228
+ {nextIndex !== undefined && <ModalRenderer index={nextIndex} />}
229
+ </>
230
+ )
231
+ })
236
232
 
237
233
  HeadlessModal.displayName = 'HeadlessModal'
238
234
  export default HeadlessModal