@inertiaui/modal-react 2.0.0-beta.1 → 3.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.
@@ -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?: string;
6
+ method?: HttpMethod;
7
7
  data?: RequestPayload;
8
8
  as?: ElementType;
9
9
  headers?: Record<string, string>;
@@ -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;
@@ -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 = /* @__PURE__ */ new Map();
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 = (options.method ?? "get").toLowerCase();
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 cached = getCachedResponse(url, method, mergedData);
125
- if (cached) {
149
+ const cacheKey = ResponseCache.key(method, url, mergedData);
150
+ if (prefetchCache.get(cacheKey)) {
126
151
  return Promise.resolve();
127
152
  }
128
- const cacheKey = getPrefetchCacheKey(url, method, mergedData);
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": pageVersion ?? "",
164
+ "X-Inertia-Version": currentPageVersion ?? "",
141
165
  "X-InertiaUI-Modal": generateId(),
142
166
  "X-InertiaUI-Modal-Base-Url": baseUrl ?? ""
143
167
  };
144
- const request = Axios({
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
- setCachedResponse(url, method, mergedData, response, cacheFor);
174
+ prefetchCache.set(cacheKey, response, cacheFor);
151
175
  options.onPrefetched?.();
152
176
  return response;
153
177
  }).finally(() => {
154
- prefetchInFlight.delete(cacheKey);
178
+ prefetchCache.deleteInFlight(cacheKey);
155
179
  });
156
- prefetchInFlight.set(cacheKey, request);
180
+ prefetchCache.setInFlight(cacheKey, request);
157
181
  return request.then(() => {
158
182
  });
159
183
  }
@@ -295,14 +319,14 @@ const ModalStackProvider = ({ children }) => {
295
319
  if (!this.response?.url) {
296
320
  return;
297
321
  }
298
- const method = (options.method ?? "get").toLowerCase();
322
+ const method = options.method ?? "get";
299
323
  const data = options.data ?? {};
300
324
  options.onStart?.();
301
- Axios({
325
+ http.getClient().request({
302
326
  url: this.response.url,
303
327
  method,
304
- data: method === "get" ? {} : data,
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 = getCachedResponse(url, method, data);
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(cachedResponse.data, config, onClose, onAfterLeave).then((modal) => {
442
- updateBrowserUrl(cachedResponse.data.url, useBrowserHistory, cachedResponse.data);
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": pageVersion ?? "",
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
- Axios({
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(response.data, config, onClose, onAfterLeave).then((modal) => {
469
- updateBrowserUrl(response.data.url, useBrowserHistory, response.data);
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
- pageVersion = pageProps.initialPage.version ?? null;
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 initialModalStillOpenedRef = useRef(!!$page.props?._inertiaui_modal);
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;