@inertiaui/modal-react 2.0.2 → 3.0.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/dist/ModalLink.d.ts +2 -2
- package/dist/cache.d.ts +12 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/inertiaui-modal.js +95 -78
- package/dist/inertiaui-modal.js.map +1 -1
- package/dist/inertiaui-modal.umd.cjs +95 -77
- package/dist/inertiaui-modal.umd.cjs.map +1 -1
- package/dist/inertiauiModal.d.ts +1 -1
- package/dist/types.d.ts +12 -12
- package/package.json +6 -8
- package/src/HeadlessModal.tsx +2 -2
- package/src/ModalLink.tsx +2 -2
- package/src/ModalRoot.tsx +97 -127
- package/src/cache.ts +64 -0
- package/src/helpers.ts +3 -0
- package/src/inertiauiModal.ts +1 -0
- package/src/types.ts +13 -13
package/dist/ModalLink.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ReactNode, ElementType } from 'react';
|
|
2
|
-
import { PrefetchOption } from './types';
|
|
2
|
+
import { PrefetchOption, HttpMethod } from './types';
|
|
3
3
|
import { RequestPayload } from '@inertiajs/core';
|
|
4
4
|
interface ModalLinkProps {
|
|
5
5
|
href: string;
|
|
6
|
-
method?:
|
|
6
|
+
method?: HttpMethod;
|
|
7
7
|
data?: RequestPayload;
|
|
8
8
|
as?: ElementType;
|
|
9
9
|
headers?: Record<string, string>;
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class ResponseCache<T> {
|
|
2
|
+
private cache;
|
|
3
|
+
private timers;
|
|
4
|
+
private inFlight;
|
|
5
|
+
static key(method: string, url: string, data: unknown): string;
|
|
6
|
+
get(key: string): T | null;
|
|
7
|
+
set(key: string, response: T, cacheFor: number): void;
|
|
8
|
+
delete(key: string): void;
|
|
9
|
+
getInFlight(key: string): Promise<T> | undefined;
|
|
10
|
+
setInFlight(key: string, promise: Promise<T>): void;
|
|
11
|
+
deleteInFlight(key: string): void;
|
|
12
|
+
}
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { sameUrlPath, except, only, rejectNullValues, kebabCase, isStandardDomEvent } from '@inertiaui/vanilla';
|
|
2
|
+
export declare function parseResponseData(data: unknown): unknown;
|
|
2
3
|
export declare function generateIdUsing(callback: (() => string) | null): void;
|
|
3
4
|
export declare function generateId(prefix?: string): string;
|
package/dist/inertiaui-modal.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React, { createContext, useContext, useRef, useEffect, useReducer, useState, createElement, useMemo, forwardRef, useImperativeHandle, useCallback } from "react";
|
|
1
|
+
import React, { createContext, useContext, useRef, useLayoutEffect, useEffect, useReducer, useState, createElement, useMemo, forwardRef, useImperativeHandle, useCallback } from "react";
|
|
2
2
|
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
-
import Axios from "axios";
|
|
4
3
|
import { generateId as generateId$1, sameUrlPath, kebabCase, except, animate, createFocusTrap, onEscapeKey, cancelAnimations, lockScroll, markAriaHidden, isStandardDomEvent, rejectNullValues, only } from "@inertiaui/vanilla";
|
|
5
4
|
import * as vanilla from "@inertiaui/vanilla";
|
|
6
|
-
import { usePage, router, progress } from "@inertiajs/react";
|
|
5
|
+
import { usePage, http, router, progress } from "@inertiajs/react";
|
|
7
6
|
import { mergeDataIntoQueryString } from "@inertiajs/core";
|
|
8
7
|
import { createPortal } from "react-dom";
|
|
9
8
|
const defaultConfig = {
|
|
@@ -77,56 +76,81 @@ const resetConfig = () => configInstance.reset();
|
|
|
77
76
|
const putConfig = (key, value) => configInstance.put(key, value);
|
|
78
77
|
const getConfig = (key) => configInstance.get(key);
|
|
79
78
|
const getConfigByType = (isSlideover, key) => configInstance.get(isSlideover ? `slideover.${key}` : `modal.${key}`);
|
|
79
|
+
function parseResponseData(data) {
|
|
80
|
+
return typeof data === "string" ? JSON.parse(data) : data;
|
|
81
|
+
}
|
|
80
82
|
function generateId(prefix = "inertiaui_") {
|
|
81
83
|
return generateId$1(prefix);
|
|
82
84
|
}
|
|
85
|
+
class ResponseCache {
|
|
86
|
+
constructor() {
|
|
87
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
88
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
89
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
90
|
+
}
|
|
91
|
+
static key(method, url, data) {
|
|
92
|
+
return `${method}:${url}:${JSON.stringify(data)}`;
|
|
93
|
+
}
|
|
94
|
+
get(key) {
|
|
95
|
+
const cached = this.cache.get(key);
|
|
96
|
+
if (!cached) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (Date.now() > cached.expiresAt) {
|
|
100
|
+
this.delete(key);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return cached.response;
|
|
104
|
+
}
|
|
105
|
+
set(key, response, cacheFor) {
|
|
106
|
+
this.delete(key);
|
|
107
|
+
this.cache.set(key, {
|
|
108
|
+
response,
|
|
109
|
+
expiresAt: Date.now() + cacheFor
|
|
110
|
+
});
|
|
111
|
+
if (cacheFor > 0) {
|
|
112
|
+
this.timers.set(key, setTimeout(() => this.delete(key), cacheFor));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
delete(key) {
|
|
116
|
+
this.cache.delete(key);
|
|
117
|
+
const timer = this.timers.get(key);
|
|
118
|
+
if (timer) {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
this.timers.delete(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
getInFlight(key) {
|
|
124
|
+
return this.inFlight.get(key);
|
|
125
|
+
}
|
|
126
|
+
setInFlight(key, promise) {
|
|
127
|
+
this.inFlight.set(key, promise);
|
|
128
|
+
}
|
|
129
|
+
deleteInFlight(key) {
|
|
130
|
+
this.inFlight.delete(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
83
133
|
const ModalStackContext = createContext(null);
|
|
84
134
|
ModalStackContext.displayName = "ModalStackContext";
|
|
85
|
-
let pageVersion = null;
|
|
86
|
-
let resolveComponent = null;
|
|
87
135
|
let baseUrl = null;
|
|
136
|
+
let currentPageVersion = null;
|
|
88
137
|
let closingToBaseUrlTarget = null;
|
|
89
|
-
const prefetchCache =
|
|
90
|
-
const prefetchInFlight = /* @__PURE__ */ new Map();
|
|
91
|
-
function getPrefetchCacheKey(url, method, data) {
|
|
92
|
-
return `${method}:${url}:${JSON.stringify(data)}`;
|
|
93
|
-
}
|
|
94
|
-
function getCachedResponse(url, method, data) {
|
|
95
|
-
const key = getPrefetchCacheKey(url, method, data);
|
|
96
|
-
const cached = prefetchCache.get(key);
|
|
97
|
-
if (!cached) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
if (Date.now() > cached.expiresAt) {
|
|
101
|
-
prefetchCache.delete(key);
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return cached.response;
|
|
105
|
-
}
|
|
106
|
-
function setCachedResponse(url, method, data, response, cacheFor) {
|
|
107
|
-
const key = getPrefetchCacheKey(url, method, data);
|
|
108
|
-
prefetchCache.set(key, {
|
|
109
|
-
response,
|
|
110
|
-
timestamp: Date.now(),
|
|
111
|
-
expiresAt: Date.now() + cacheFor
|
|
112
|
-
});
|
|
113
|
-
}
|
|
138
|
+
const prefetchCache = new ResponseCache();
|
|
114
139
|
function prefetch(href, options = {}) {
|
|
115
140
|
if (href.startsWith("#")) {
|
|
116
141
|
return Promise.resolve();
|
|
117
142
|
}
|
|
118
|
-
const method =
|
|
143
|
+
const method = options.method ?? "get";
|
|
119
144
|
const data = options.data ?? {};
|
|
120
145
|
const headers = options.headers ?? {};
|
|
121
146
|
const queryStringArrayFormat = options.queryStringArrayFormat ?? "brackets";
|
|
122
147
|
const cacheFor = options.cacheFor ?? 3e4;
|
|
123
148
|
const [url, mergedData] = mergeDataIntoQueryString(method, href || "", data, queryStringArrayFormat);
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
149
|
+
const cacheKey = ResponseCache.key(method, url, mergedData);
|
|
150
|
+
if (prefetchCache.get(cacheKey)) {
|
|
126
151
|
return Promise.resolve();
|
|
127
152
|
}
|
|
128
|
-
const
|
|
129
|
-
const inFlight = prefetchInFlight.get(cacheKey);
|
|
153
|
+
const inFlight = prefetchCache.getInFlight(cacheKey);
|
|
130
154
|
if (inFlight) {
|
|
131
155
|
return inFlight.then(() => {
|
|
132
156
|
});
|
|
@@ -137,23 +161,23 @@ function prefetch(href, options = {}) {
|
|
|
137
161
|
Accept: "text/html, application/xhtml+xml",
|
|
138
162
|
"X-Requested-With": "XMLHttpRequest",
|
|
139
163
|
"X-Inertia": "true",
|
|
140
|
-
"X-Inertia-Version":
|
|
164
|
+
"X-Inertia-Version": currentPageVersion ?? "",
|
|
141
165
|
"X-InertiaUI-Modal": generateId(),
|
|
142
166
|
"X-InertiaUI-Modal-Base-Url": baseUrl ?? ""
|
|
143
167
|
};
|
|
144
|
-
const request =
|
|
168
|
+
const request = http.getClient().request({
|
|
145
169
|
url,
|
|
146
170
|
method,
|
|
147
171
|
data: mergedData,
|
|
148
172
|
headers: requestHeaders
|
|
149
173
|
}).then((response) => {
|
|
150
|
-
|
|
174
|
+
prefetchCache.set(cacheKey, response, cacheFor);
|
|
151
175
|
options.onPrefetched?.();
|
|
152
176
|
return response;
|
|
153
177
|
}).finally(() => {
|
|
154
|
-
|
|
178
|
+
prefetchCache.deleteInFlight(cacheKey);
|
|
155
179
|
});
|
|
156
|
-
|
|
180
|
+
prefetchCache.setInFlight(cacheKey, request);
|
|
157
181
|
return request.then(() => {
|
|
158
182
|
});
|
|
159
183
|
}
|
|
@@ -241,7 +265,7 @@ const ModalStackProvider = ({ children }) => {
|
|
|
241
265
|
const savedBaseUrl = baseUrl;
|
|
242
266
|
baseUrl = null;
|
|
243
267
|
closingToBaseUrlTarget = savedBaseUrl;
|
|
244
|
-
if (savedBaseUrl && typeof window !== "undefined") {
|
|
268
|
+
if (savedBaseUrl && typeof window !== "undefined" && !sameUrlPath(savedBaseUrl, window.location.href)) {
|
|
245
269
|
router.push({
|
|
246
270
|
url: savedBaseUrl,
|
|
247
271
|
preserveScroll: true,
|
|
@@ -295,14 +319,14 @@ const ModalStackProvider = ({ children }) => {
|
|
|
295
319
|
if (!this.response?.url) {
|
|
296
320
|
return;
|
|
297
321
|
}
|
|
298
|
-
const method =
|
|
322
|
+
const method = options.method ?? "get";
|
|
299
323
|
const data = options.data ?? {};
|
|
300
324
|
options.onStart?.();
|
|
301
|
-
|
|
325
|
+
http.getClient().request({
|
|
302
326
|
url: this.response.url,
|
|
303
327
|
method,
|
|
304
|
-
data: method === "get" ?
|
|
305
|
-
params: method === "get" ? data :
|
|
328
|
+
data: method === "get" ? void 0 : data,
|
|
329
|
+
params: method === "get" ? data : void 0,
|
|
306
330
|
headers: {
|
|
307
331
|
...options.headers ?? {},
|
|
308
332
|
Accept: "text/html, application/xhtml+xml",
|
|
@@ -314,7 +338,7 @@ const ModalStackProvider = ({ children }) => {
|
|
|
314
338
|
"X-InertiaUI-Modal-Base-Url": baseUrl ?? ""
|
|
315
339
|
}
|
|
316
340
|
}).then((response2) => {
|
|
317
|
-
this.updateProps(response2.data.props);
|
|
341
|
+
this.updateProps(parseResponseData(response2.data).props);
|
|
318
342
|
options.onSuccess?.(response2);
|
|
319
343
|
}).catch((error) => {
|
|
320
344
|
options.onError?.(error);
|
|
@@ -346,9 +370,6 @@ const ModalStackProvider = ({ children }) => {
|
|
|
346
370
|
return typeof data === "object" && data !== null && "component" in data && typeof data.component === "string";
|
|
347
371
|
};
|
|
348
372
|
const pushFromResponseData = (responseData, config = {}, onClose = null, onAfterLeave = null) => {
|
|
349
|
-
if (!resolveComponent) {
|
|
350
|
-
return Promise.reject(new Error("resolveComponent not set"));
|
|
351
|
-
}
|
|
352
373
|
if (!isValidModalResponse(responseData)) {
|
|
353
374
|
return Promise.reject(
|
|
354
375
|
new Error(
|
|
@@ -356,7 +377,7 @@ const ModalStackProvider = ({ children }) => {
|
|
|
356
377
|
)
|
|
357
378
|
);
|
|
358
379
|
}
|
|
359
|
-
return resolveComponent(responseData.component).then(
|
|
380
|
+
return router.resolveComponent(responseData.component).then(
|
|
360
381
|
(component) => push(component, responseData, config, onClose, onAfterLeave)
|
|
361
382
|
);
|
|
362
383
|
};
|
|
@@ -435,11 +456,12 @@ const ModalStackProvider = ({ children }) => {
|
|
|
435
456
|
return;
|
|
436
457
|
}
|
|
437
458
|
const [url, data] = mergeDataIntoQueryString(method, href || "", payload, queryStringArrayFormat);
|
|
438
|
-
const cachedResponse =
|
|
459
|
+
const cachedResponse = prefetchCache.get(ResponseCache.key(method, url, data));
|
|
439
460
|
if (cachedResponse) {
|
|
461
|
+
const cachedData = parseResponseData(cachedResponse.data);
|
|
440
462
|
onSuccess?.(cachedResponse);
|
|
441
|
-
pushFromResponseData(
|
|
442
|
-
updateBrowserUrl(
|
|
463
|
+
pushFromResponseData(cachedData, config, onClose, onAfterLeave).then((modal) => {
|
|
464
|
+
updateBrowserUrl(cachedData.url, useBrowserHistory, cachedData);
|
|
443
465
|
resolve(modal);
|
|
444
466
|
}).catch(reject);
|
|
445
467
|
return;
|
|
@@ -452,21 +474,22 @@ const ModalStackProvider = ({ children }) => {
|
|
|
452
474
|
Accept: "text/html, application/xhtml+xml",
|
|
453
475
|
"X-Requested-With": "XMLHttpRequest",
|
|
454
476
|
"X-Inertia": "true",
|
|
455
|
-
"X-Inertia-Version":
|
|
477
|
+
"X-Inertia-Version": currentPageVersion ?? "",
|
|
456
478
|
"X-InertiaUI-Modal": modalId,
|
|
457
479
|
"X-InertiaUI-Modal-Base-Url": baseUrl ?? ""
|
|
458
480
|
};
|
|
459
481
|
onStart?.();
|
|
460
482
|
progress?.start();
|
|
461
|
-
|
|
483
|
+
http.getClient().request({
|
|
462
484
|
url,
|
|
463
485
|
method,
|
|
464
486
|
data,
|
|
465
487
|
headers: requestHeaders
|
|
466
488
|
}).then((response) => {
|
|
489
|
+
const responseData = parseResponseData(response.data);
|
|
467
490
|
onSuccess?.(response);
|
|
468
|
-
pushFromResponseData(
|
|
469
|
-
updateBrowserUrl(
|
|
491
|
+
pushFromResponseData(responseData, config, onClose, onAfterLeave).then((modal) => {
|
|
492
|
+
updateBrowserUrl(responseData.url, useBrowserHistory, responseData);
|
|
470
493
|
resolve(modal);
|
|
471
494
|
}).catch(reject);
|
|
472
495
|
}).catch((...args) => {
|
|
@@ -523,10 +546,7 @@ const useModalStack = () => {
|
|
|
523
546
|
const modalPropNames = ["closeButton", "closeExplicitly", "closeOnClickOutside", "maxWidth", "paddingClasses", "panelClasses", "position", "slideover"];
|
|
524
547
|
const initFromPageProps = (pageProps) => {
|
|
525
548
|
if (pageProps.initialPage) {
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
if (pageProps.resolveComponent) {
|
|
529
|
-
resolveComponent = pageProps.resolveComponent;
|
|
549
|
+
currentPageVersion = pageProps.initialPage.version ?? null;
|
|
530
550
|
}
|
|
531
551
|
};
|
|
532
552
|
const renderApp = (App, pageProps) => {
|
|
@@ -556,9 +576,19 @@ const ModalRoot = ({ children }) => {
|
|
|
556
576
|
const context = useContext(ModalStackContext);
|
|
557
577
|
const $page = usePage();
|
|
558
578
|
const pendingModalKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
579
|
+
currentPageVersion = $page.version ?? null;
|
|
559
580
|
const getModalKey = (modalData) => modalData.id || `${modalData.component}:${modalData.url}`;
|
|
560
581
|
const isNavigatingRef = useRef(false);
|
|
561
|
-
const
|
|
582
|
+
const pageRef = useRef($page);
|
|
583
|
+
pageRef.current = $page;
|
|
584
|
+
useLayoutEffect(() => http.onRequest((config) => {
|
|
585
|
+
const baseUrlValue = baseUrl ?? pageRef.current.props._inertiaui_modal?.baseUrl ?? null;
|
|
586
|
+
if (baseUrlValue) {
|
|
587
|
+
config.headers = config.headers ?? {};
|
|
588
|
+
config.headers["X-InertiaUI-Modal-Base-Url"] = baseUrlValue;
|
|
589
|
+
}
|
|
590
|
+
return config;
|
|
591
|
+
}), []);
|
|
562
592
|
useEffect(() => router.on("start", () => isNavigatingRef.current = true), []);
|
|
563
593
|
useEffect(() => router.on("finish", () => isNavigatingRef.current = false), []);
|
|
564
594
|
useEffect(
|
|
@@ -572,7 +602,6 @@ const ModalRoot = ({ children }) => {
|
|
|
572
602
|
closingToBaseUrlTarget = null;
|
|
573
603
|
context?.closeAll(true);
|
|
574
604
|
baseUrl = null;
|
|
575
|
-
initialModalStillOpenedRef.current = false;
|
|
576
605
|
return;
|
|
577
606
|
}
|
|
578
607
|
closingToBaseUrlTarget = null;
|
|
@@ -580,13 +609,11 @@ const ModalRoot = ({ children }) => {
|
|
|
580
609
|
if (!modalOnBase) {
|
|
581
610
|
context?.closeAll(true);
|
|
582
611
|
baseUrl = null;
|
|
583
|
-
initialModalStillOpenedRef.current = false;
|
|
584
612
|
return;
|
|
585
613
|
}
|
|
586
614
|
if (!sameUrlPath(pageUrl, modalOnBase.url)) {
|
|
587
615
|
context?.closeAll(true);
|
|
588
616
|
baseUrl = null;
|
|
589
|
-
initialModalStillOpenedRef.current = false;
|
|
590
617
|
return;
|
|
591
618
|
}
|
|
592
619
|
const modalKey = getModalKey(modalOnBase);
|
|
@@ -606,6 +633,7 @@ const ModalRoot = ({ children }) => {
|
|
|
606
633
|
console.error("No base url in modal response data so cannot navigate back");
|
|
607
634
|
return;
|
|
608
635
|
}
|
|
636
|
+
baseUrl = null;
|
|
609
637
|
if (!isNavigatingRef.current && typeof window !== "undefined" && window.location.href !== modalOnBase.baseUrl) {
|
|
610
638
|
router.visit(modalOnBase.baseUrl, {
|
|
611
639
|
preserveScroll: true,
|
|
@@ -618,17 +646,6 @@ const ModalRoot = ({ children }) => {
|
|
|
618
646
|
}),
|
|
619
647
|
[]
|
|
620
648
|
);
|
|
621
|
-
const axiosRequestInterceptor = (config) => {
|
|
622
|
-
const baseUrlValue = baseUrl ?? (initialModalStillOpenedRef.current ? $page.props._inertiaui_modal?.baseUrl : null);
|
|
623
|
-
if (baseUrlValue) {
|
|
624
|
-
config.headers["X-InertiaUI-Modal-Base-Url"] = baseUrlValue;
|
|
625
|
-
}
|
|
626
|
-
return config;
|
|
627
|
-
};
|
|
628
|
-
useEffect(() => {
|
|
629
|
-
const interceptorId = Axios.interceptors.request.use(axiosRequestInterceptor);
|
|
630
|
-
return () => Axios.interceptors.request.eject(interceptorId);
|
|
631
|
-
}, []);
|
|
632
649
|
const previousModalRef = useRef(void 0);
|
|
633
650
|
useEffect(() => {
|
|
634
651
|
const newModal = $page.props?._inertiaui_modal;
|
|
@@ -742,7 +759,7 @@ const HeadlessModal = forwardRef(
|
|
|
742
759
|
}, [modalContext]);
|
|
743
760
|
const previousIsOpenRef = useRef(void 0);
|
|
744
761
|
useEffect(() => {
|
|
745
|
-
if (modalContext
|
|
762
|
+
if (modalContext != null) {
|
|
746
763
|
if (modalContext.isOpen) {
|
|
747
764
|
onSuccess?.();
|
|
748
765
|
} else if (previousIsOpenRef.current === true) {
|
|
@@ -753,7 +770,7 @@ const HeadlessModal = forwardRef(
|
|
|
753
770
|
}, [modalContext?.isOpen]);
|
|
754
771
|
const [rendered, setRendered] = useState(false);
|
|
755
772
|
useEffect(() => {
|
|
756
|
-
if (rendered && modalContext
|
|
773
|
+
if (rendered && modalContext != null && modalContext.isOpen) {
|
|
757
774
|
if (modalContext.onTopOfStack) {
|
|
758
775
|
onFocus?.();
|
|
759
776
|
} else {
|