UNPKG

48.9 kBJavaScriptView Raw
1/**
2 * react-router v8.4.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { ABSOLUTE_URL_REGEX } from "../router/url.js";
12import { createBrowserHistory, createHashHistory, createPath, invariant, warning } from "../router/history.js";
13import { ErrorResponseImpl, SUPPORTED_ERROR_TYPES, defaultMapRouteProperties, joinPaths, matchPath, parseToInfo, resolveTo, stripBasename } from "../router/utils.js";
14import { IDLE_FETCHER, createRouter } from "../router/router.js";
15import { DataRouterNavigationContext, NavigationContext, RouteContext, ViewTransitionContext } from "../context.js";
16import { useBlocker, useCurrentRouteId, useDataRouterContext, useDataRouterFetchers, useDataRouterState, useHref, useLocation, useMatches, useNavigate, useNavigation, useResolvedPath } from "../hooks.js";
17import { Router, hydrationRouteProperties } from "../components.js";
18import { createSearchParams, getFormSubmissionInfo, getSearchParamsForLocation, shouldProcessLinkClick } from "./dom.js";
19import { escapeHtml } from "./ssr/markup.js";
20import { FrameworkContext, PrefetchPageLinks, mergeRefs, usePrefetchBehavior } from "./ssr/components.js";
21import * as React$1 from "react";
22//#region lib/dom/lib.tsx
23const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
24try {
25 if (isBrowser) window.__reactRouterVersion = "8";
26} catch {}
27/**
28* Create a new {@link DataRouter| data router} that manages the application
29* path via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
30* and [`history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState).
31*
32* Data Routers should not be held in React state. You should create your router
33* once outside of the React tree and pass it to {@link RouterProvider | `<RouterProvider>`}.
34* You can use `patchRoutesOnNavigation` to add additional routes programmatically.
35*
36* @public
37* @category Data Routers
38* @mode data
39* @param routes Application routes
40* @param opts Options
41* @param {DOMRouterOpts.basename} opts.basename n/a
42* @param {DOMRouterOpts.dataStrategy} opts.dataStrategy n/a
43* @param {DOMRouterOpts.future} opts.future n/a
44* @param {DOMRouterOpts.getContext} opts.getContext n/a
45* @param {DOMRouterOpts.hydrationData} opts.hydrationData n/a
46* @param {DOMRouterOpts.instrumentations} opts.instrumentations n/a
47* @param {DOMRouterOpts.patchRoutesOnNavigation} opts.patchRoutesOnNavigation n/a
48* @param {DOMRouterOpts.window} opts.window n/a
49* @returns An initialized {@link DataRouter| data router} to pass to {@link RouterProvider | `<RouterProvider>`}
50*/
51function createBrowserRouter(routes, opts) {
52 return createRouter({
53 basename: opts?.basename,
54 getContext: opts?.getContext,
55 future: opts?.future,
56 history: createBrowserHistory({ window: opts?.window }),
57 hydrationData: opts?.hydrationData || parseHydrationData(),
58 routes,
59 mapRouteProperties: defaultMapRouteProperties,
60 hydrationRouteProperties,
61 dataStrategy: opts?.dataStrategy,
62 patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
63 window: opts?.window,
64 instrumentations: opts?.instrumentations
65 }).initialize();
66}
67/**
68* Create a new {@link DataRouter| data router} that manages the application
69* path via the URL [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash).
70*
71* Data Routers should not be held in React state. You should create your router
72* once outside of the React tree and pass it to {@link RouterProvider | `<RouterProvider>`}.
73* You can use `patchRoutesOnNavigation` to add additional routes programmatically.
74*
75* @public
76* @category Data Routers
77* @mode data
78* @param routes Application routes
79* @param opts Options
80* @param {DOMRouterOpts.basename} opts.basename n/a
81* @param {DOMRouterOpts.future} opts.future n/a
82* @param {DOMRouterOpts.getContext} opts.getContext n/a
83* @param {DOMRouterOpts.hydrationData} opts.hydrationData n/a
84* @param {DOMRouterOpts.instrumentations} opts.instrumentations n/a
85* @param {DOMRouterOpts.dataStrategy} opts.dataStrategy n/a
86* @param {DOMRouterOpts.patchRoutesOnNavigation} opts.patchRoutesOnNavigation n/a
87* @param {DOMRouterOpts.window} opts.window n/a
88* @returns An initialized {@link DataRouter| data router} to pass to {@link RouterProvider | `<RouterProvider>`}
89*/
90function createHashRouter(routes, opts) {
91 return createRouter({
92 basename: opts?.basename,
93 getContext: opts?.getContext,
94 future: opts?.future,
95 history: createHashHistory({ window: opts?.window }),
96 hydrationData: opts?.hydrationData || parseHydrationData(),
97 routes,
98 mapRouteProperties: defaultMapRouteProperties,
99 hydrationRouteProperties,
100 dataStrategy: opts?.dataStrategy,
101 patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
102 window: opts?.window,
103 instrumentations: opts?.instrumentations
104 }).initialize();
105}
106function parseHydrationData() {
107 let state = window?.__staticRouterHydrationData;
108 if (state && state.errors) state = {
109 ...state,
110 errors: deserializeErrors(state.errors)
111 };
112 return state;
113}
114function deserializeErrors(errors) {
115 if (!errors) return null;
116 let entries = Object.entries(errors);
117 let serialized = {};
118 for (let [key, val] of entries) if (val && val.__type === "RouteErrorResponse") serialized[key] = new ErrorResponseImpl(val.status, val.statusText, val.data, val.internal === true);
119 else if (val && val.__type === "Error") {
120 if (typeof val.__subType === "string" && SUPPORTED_ERROR_TYPES.includes(val.__subType)) {
121 let ErrorConstructor = window[val.__subType];
122 if (typeof ErrorConstructor === "function") try {
123 let error = new ErrorConstructor(val.message);
124 error.stack = "";
125 serialized[key] = error;
126 } catch {}
127 }
128 if (serialized[key] == null) {
129 let error = new Error(val.message);
130 error.stack = "";
131 serialized[key] = error;
132 }
133 } else serialized[key] = val;
134 return serialized;
135}
136/**
137* A declarative {@link Router | `<Router>`} using the browser [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
138* API for client-side routing.
139*
140* @public
141* @category Declarative Routers
142* @mode declarative
143* @param props Props
144* @param {BrowserRouterProps.basename} props.basename n/a
145* @param {BrowserRouterProps.children} props.children n/a
146* @param {BrowserRouterProps.useTransitions} props.useTransitions n/a
147* @param {BrowserRouterProps.window} props.window n/a
148* @returns A declarative {@link Router | `<Router>`} using the browser [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
149* API for client-side routing.
150*/
151function BrowserRouter({ basename, children, useTransitions, window }) {
152 let historyRef = React$1.useRef(null);
153 if (historyRef.current == null) historyRef.current = createBrowserHistory({
154 window,
155 v5Compat: true
156 });
157 let history = historyRef.current;
158 let [state, setStateImpl] = React$1.useState({
159 action: history.action,
160 location: history.location
161 });
162 let setState = React$1.useCallback((newState) => {
163 if (useTransitions === false) setStateImpl(newState);
164 else React$1.startTransition(() => setStateImpl(newState));
165 }, [useTransitions]);
166 React$1.useLayoutEffect(() => history.listen(setState), [history, setState]);
167 return /* @__PURE__ */ React$1.createElement(Router, {
168 basename,
169 children,
170 location: state.location,
171 navigationType: state.action,
172 navigator: history,
173 useTransitions
174 });
175}
176/**
177* A declarative {@link Router | `<Router>`} that stores the location in the
178* [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) portion
179* of the URL so it is not sent to the server.
180*
181* @public
182* @category Declarative Routers
183* @mode declarative
184* @param props Props
185* @param {HashRouterProps.basename} props.basename n/a
186* @param {HashRouterProps.children} props.children n/a
187* @param {HashRouterProps.useTransitions} props.useTransitions n/a
188* @param {HashRouterProps.window} props.window n/a
189* @returns A declarative {@link Router | `<Router>`} using the URL [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash)
190* for client-side routing.
191*/
192function HashRouter({ basename, children, useTransitions, window }) {
193 let historyRef = React$1.useRef(null);
194 if (historyRef.current == null) historyRef.current = createHashHistory({
195 window,
196 v5Compat: true
197 });
198 let history = historyRef.current;
199 let [state, setStateImpl] = React$1.useState({
200 action: history.action,
201 location: history.location
202 });
203 let setState = React$1.useCallback((newState) => {
204 if (useTransitions === false) setStateImpl(newState);
205 else React$1.startTransition(() => setStateImpl(newState));
206 }, [useTransitions]);
207 React$1.useLayoutEffect(() => history.listen(setState), [history, setState]);
208 return /* @__PURE__ */ React$1.createElement(Router, {
209 basename,
210 children,
211 location: state.location,
212 navigationType: state.action,
213 navigator: history,
214 useTransitions
215 });
216}
217/**
218* A declarative {@link Router | `<Router>`} that accepts a pre-instantiated
219* `history` object.
220* It's important to note that using your own `history` object is highly discouraged
221* and may add two versions of the `history` library to your bundles unless you use
222* the same version of the `history` library that React Router uses internally.
223*
224* @name unstable_HistoryRouter
225* @public
226* @category Declarative Routers
227* @mode declarative
228* @param props Props
229* @param {HistoryRouterProps.basename} props.basename n/a
230* @param {HistoryRouterProps.children} props.children n/a
231* @param {HistoryRouterProps.history} props.history n/a
232* @param {HistoryRouterProps.useTransitions} props.useTransitions n/a
233* @returns A declarative {@link Router | `<Router>`} using the provided history
234* implementation for client-side routing.
235*/
236function HistoryRouter({ basename, children, history, useTransitions }) {
237 let [state, setStateImpl] = React$1.useState({
238 action: history.action,
239 location: history.location
240 });
241 let setState = React$1.useCallback((newState) => {
242 if (useTransitions === false) setStateImpl(newState);
243 else React$1.startTransition(() => setStateImpl(newState));
244 }, [useTransitions]);
245 React$1.useLayoutEffect(() => history.listen(setState), [history, setState]);
246 return /* @__PURE__ */ React$1.createElement(Router, {
247 basename,
248 children,
249 location: state.location,
250 navigationType: state.action,
251 navigator: history,
252 useTransitions
253 });
254}
255HistoryRouter.displayName = "unstable_HistoryRouter";
256/**
257* A progressively enhanced [`<a href>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
258* wrapper to enable navigation with client-side routing.
259*
260* @example
261* import { Link } from "react-router";
262*
263* <Link to="/dashboard">Dashboard</Link>;
264*
265* <Link
266* to={{
267* pathname: "/some/path",
268* search: "?query=string",
269* hash: "#hash",
270* }}
271* />;
272*
273* @public
274* @category Components
275* @param {LinkProps.discover} props.discover [modes: framework] n/a
276* @param {LinkProps.prefetch} props.prefetch [modes: framework] n/a
277* @param {LinkProps.preventScrollReset} props.preventScrollReset [modes: framework, data] n/a
278* @param {LinkProps.relative} props.relative n/a
279* @param {LinkProps.reloadDocument} props.reloadDocument n/a
280* @param {LinkProps.replace} props.replace n/a
281* @param {LinkProps.state} props.state n/a
282* @param {LinkProps.to} props.to n/a
283* @param {LinkProps.viewTransition} props.viewTransition [modes: framework, data] n/a
284* @param {LinkProps.defaultShouldRevalidate} props.defaultShouldRevalidate n/a
285* @param {LinkProps.mask} props.mask [modes: framework, data] n/a
286*/
287const Link = React$1.forwardRef(function LinkWithRef({ onClick, discover = "render", prefetch = "none", relative, reloadDocument, replace, mask, state, target, to, preventScrollReset, viewTransition, defaultShouldRevalidate, ...rest }, forwardedRef) {
288 let { basename, navigator, useTransitions } = React$1.useContext(NavigationContext);
289 let isAbsolute = typeof to === "string" && ABSOLUTE_URL_REGEX.test(to);
290 let parsed = parseToInfo(to, basename);
291 to = parsed.to;
292 let href = useHref(to, { relative });
293 let location = useLocation();
294 let maskedHref = null;
295 if (mask) {
296 let resolved = resolveTo(mask, [], location.mask ? location.mask.pathname : "/", true);
297 if (basename !== "/") resolved.pathname = resolved.pathname === "/" ? basename : joinPaths([basename, resolved.pathname]);
298 maskedHref = navigator.createHref(resolved);
299 }
300 let [shouldPrefetch, prefetchRef, prefetchHandlers] = usePrefetchBehavior(prefetch, rest);
301 let internalOnClick = useLinkClickHandler(to, {
302 replace,
303 mask,
304 state,
305 target,
306 preventScrollReset,
307 relative,
308 viewTransition,
309 defaultShouldRevalidate,
310 useTransitions
311 });
312 function handleClick(event) {
313 if (onClick) onClick(event);
314 if (!event.defaultPrevented) internalOnClick(event);
315 }
316 let isSpaLink = !(parsed.isExternal || reloadDocument);
317 let link = /* @__PURE__ */ React$1.createElement("a", {
318 ...rest,
319 ...prefetchHandlers,
320 href: (isSpaLink ? maskedHref : void 0) || parsed.absoluteURL || href,
321 onClick: isSpaLink ? handleClick : onClick,
322 ref: mergeRefs(forwardedRef, prefetchRef),
323 target,
324 "data-discover": !isAbsolute && discover === "render" ? "true" : void 0
325 });
326 return shouldPrefetch && !isAbsolute ? /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, link, /* @__PURE__ */ React$1.createElement(PrefetchPageLinks, { page: href })) : link;
327});
328Link.displayName = "Link";
329/**
330* Wraps {@link Link | `<Link>`} with additional props for styling active and
331* pending states.
332*
333* - Automatically applies classes to the link based on its `active` and `pending`
334* states, see {@link NavLinkProps.className}
335* - Note that `pending` is only available with Framework and Data modes.
336* - Automatically applies `aria-current="page"` to the link when the link is active.
337* See [`aria-current`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current)
338* on MDN.
339* - States are additionally available through the className, style, and children
340* render props. See {@link NavLinkRenderProps}.
341*
342* @example
343* <NavLink to="/message">Messages</NavLink>
344*
345* // Using render props
346* <NavLink
347* to="/messages"
348* className={({ isActive, isPending }) =>
349* isPending ? "pending" : isActive ? "active" : ""
350* }
351* >
352* Messages
353* </NavLink>
354*
355* @public
356* @category Components
357* @param {NavLinkProps.caseSensitive} props.caseSensitive n/a
358* @param {NavLinkProps.children} props.children n/a
359* @param {NavLinkProps.className} props.className n/a
360* @param {NavLinkProps.discover} props.discover [modes: framework] n/a
361* @param {NavLinkProps.end} props.end n/a
362* @param {NavLinkProps.prefetch} props.prefetch [modes: framework] n/a
363* @param {NavLinkProps.preventScrollReset} props.preventScrollReset [modes: framework, data] n/a
364* @param {NavLinkProps.relative} props.relative n/a
365* @param {NavLinkProps.reloadDocument} props.reloadDocument n/a
366* @param {NavLinkProps.replace} props.replace n/a
367* @param {NavLinkProps.state} props.state n/a
368* @param {NavLinkProps.style} props.style n/a
369* @param {NavLinkProps.to} props.to n/a
370* @param {NavLinkProps.viewTransition} props.viewTransition [modes: framework, data] n/a
371*/
372const NavLink = React$1.forwardRef(function NavLinkWithRef({ "aria-current": ariaCurrentProp = "page", caseSensitive = false, className: classNameProp = "", end = false, style: styleProp, to, viewTransition, children, ...rest }, ref) {
373 let path = useResolvedPath(to, { relative: rest.relative });
374 let location = useLocation();
375 let routerNavigation = React$1.useContext(DataRouterNavigationContext);
376 let { navigator, basename } = React$1.useContext(NavigationContext);
377 let isTransitioning = routerNavigation != null && useViewTransitionState(path) && viewTransition === true;
378 let toPathname = navigator.encodeLocation ? navigator.encodeLocation(path).pathname : path.pathname;
379 let locationPathname = location.pathname;
380 let nextLocationPathname = routerNavigation?.navigation.location ? routerNavigation.navigation.location.pathname : null;
381 if (!caseSensitive) {
382 locationPathname = locationPathname.toLowerCase();
383 nextLocationPathname = nextLocationPathname ? nextLocationPathname.toLowerCase() : null;
384 toPathname = toPathname.toLowerCase();
385 }
386 if (nextLocationPathname && basename) nextLocationPathname = stripBasename(nextLocationPathname, basename) || nextLocationPathname;
387 const endSlashPosition = toPathname !== "/" && toPathname.endsWith("/") ? toPathname.length - 1 : toPathname.length;
388 let isActive = locationPathname === toPathname || !end && locationPathname.startsWith(toPathname) && locationPathname.charAt(endSlashPosition) === "/";
389 let isPending = nextLocationPathname != null && (nextLocationPathname === toPathname || !end && nextLocationPathname.startsWith(toPathname) && nextLocationPathname.charAt(endSlashPosition) === "/");
390 let renderProps = {
391 isActive,
392 isPending,
393 isTransitioning
394 };
395 let ariaCurrent = isActive ? ariaCurrentProp : void 0;
396 let className;
397 if (typeof classNameProp === "function") className = classNameProp(renderProps);
398 else className = [
399 classNameProp,
400 isActive ? "active" : null,
401 isPending ? "pending" : null,
402 isTransitioning ? "transitioning" : null
403 ].filter(Boolean).join(" ");
404 let style = typeof styleProp === "function" ? styleProp(renderProps) : styleProp;
405 return /* @__PURE__ */ React$1.createElement(Link, {
406 ...rest,
407 "aria-current": ariaCurrent,
408 className,
409 ref,
410 style,
411 to,
412 viewTransition
413 }, typeof children === "function" ? children(renderProps) : children);
414});
415NavLink.displayName = "NavLink";
416/**
417* A progressively enhanced HTML [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
418* that submits data to actions via [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch),
419* activating pending states in {@link useNavigation} which enables advanced
420* user interfaces beyond a basic HTML [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form).
421* After a form's `action` completes, all data on the page is automatically
422* revalidated to keep the UI in sync with the data.
423*
424* Because it uses the HTML form API, server rendered pages are interactive at a
425* basic level before JavaScript loads. Instead of React Router managing the
426* submission, the browser manages the submission as well as the pending states
427* (like the spinning favicon). After JavaScript loads, React Router takes over
428* enabling web application user experiences.
429*
430* `Form` is most useful for submissions that should also change the URL or
431* otherwise add an entry to the browser history stack. For forms that shouldn't
432* manipulate the browser [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
433* stack, use {@link FetcherWithComponents.Form | `<fetcher.Form>`}.
434*
435* @example
436* import { Form } from "react-router";
437*
438* function NewEvent() {
439* return (
440* <Form action="/events" method="post">
441* <input name="title" type="text" />
442* <input name="description" type="text" />
443* </Form>
444* );
445* }
446*
447* @public
448* @category Components
449* @mode framework
450* @mode data
451* @param {FormProps.action} action n/a
452* @param {FormProps.discover} discover n/a
453* @param {FormProps.encType} encType n/a
454* @param {FormProps.fetcherKey} fetcherKey n/a
455* @param {FormProps.method} method n/a
456* @param {FormProps.navigate} navigate n/a
457* @param {FormProps.preventScrollReset} preventScrollReset n/a
458* @param {FormProps.relative} relative n/a
459* @param {FormProps.reloadDocument} reloadDocument n/a
460* @param {FormProps.replace} replace n/a
461* @param {FormProps.state} state n/a
462* @param {FormProps.viewTransition} viewTransition n/a
463* @param {FormProps.defaultShouldRevalidate} defaultShouldRevalidate n/a
464* @returns A progressively enhanced [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) component
465*/
466const Form = React$1.forwardRef(({ discover = "render", fetcherKey, navigate, reloadDocument, replace, state, method = "get", action, onSubmit, relative, preventScrollReset, viewTransition, defaultShouldRevalidate, ...props }, forwardedRef) => {
467 let { useTransitions } = React$1.useContext(NavigationContext);
468 let submit = useSubmit();
469 let formAction = useFormAction(action, { relative });
470 let formMethod = method.toLowerCase() === "get" ? "get" : "post";
471 let isAbsolute = typeof action === "string" && ABSOLUTE_URL_REGEX.test(action);
472 let submitHandler = (event) => {
473 onSubmit && onSubmit(event);
474 if (event.defaultPrevented) return;
475 event.preventDefault();
476 let submitter = event.nativeEvent.submitter;
477 let submitMethod = submitter?.getAttribute("formmethod") || method;
478 let doSubmit = () => submit(submitter || event.currentTarget, {
479 fetcherKey,
480 method: submitMethod,
481 navigate,
482 replace,
483 state,
484 relative,
485 preventScrollReset,
486 viewTransition,
487 defaultShouldRevalidate
488 });
489 if (useTransitions && navigate !== false) React$1.startTransition(() => doSubmit());
490 else doSubmit();
491 };
492 return /* @__PURE__ */ React$1.createElement("form", {
493 ref: forwardedRef,
494 method: formMethod,
495 action: formAction,
496 onSubmit: reloadDocument ? onSubmit : submitHandler,
497 ...props,
498 "data-discover": !isAbsolute && discover === "render" ? "true" : void 0
499 });
500});
501Form.displayName = "Form";
502/**
503* Emulates the browser's scroll restoration on location changes. Apps should only render one of these, right before the {@link Scripts} component.
504*
505* ```tsx
506* import { ScrollRestoration } from "react-router";
507*
508* export default function Root() {
509* return (
510* <html>
511* <body>
512* <ScrollRestoration />
513* <Scripts />
514* </body>
515* </html>
516* );
517* }
518* ```
519*
520* This component renders an inline `<script>` to prevent scroll flashing. The
521* `nonce` prop will be passed down to the script tag to allow CSP nonce usage.
522* If not provided in Framework Mode, it will default to any
523* {@link ServerRouter | `<ServerRouter nonce>`} prop.
524*
525* ```tsx
526* <ScrollRestoration nonce={cspNonce} />
527* ```
528*
529* @public
530* @category Components
531* @mode framework
532* @mode data
533* @param props Props
534* @param {ScrollRestorationProps.getKey} props.getKey n/a
535* @param {ScriptsProps.nonce} props.nonce n/a
536* @param {ScrollRestorationProps.storageKey} props.storageKey n/a
537* @returns A [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
538* tag that restores scroll positions on navigation.
539*/
540function ScrollRestoration({ getKey, storageKey, ...props }) {
541 let remixContext = React$1.useContext(FrameworkContext);
542 let { basename } = React$1.useContext(NavigationContext);
543 let location = useLocation();
544 let matches = useMatches();
545 useScrollRestoration({
546 getKey,
547 storageKey
548 });
549 let ssrKey = React$1.useMemo(() => {
550 if (!remixContext || !getKey) return null;
551 let userKey = getScrollRestorationKey(location, matches, basename, getKey);
552 return userKey !== location.key ? userKey : null;
553 }, []);
554 if (!remixContext || remixContext.isSpaMode) return null;
555 let restoreScroll = ((storageKey, restoreKey) => {
556 if (!window.history.state || !window.history.state.key) {
557 let key = Math.random().toString(32).slice(2);
558 window.history.replaceState({ key }, "");
559 }
560 try {
561 let storedY = JSON.parse(sessionStorage.getItem(storageKey) || "{}")[restoreKey || window.history.state.key];
562 if (typeof storedY === "number") window.scrollTo(0, storedY);
563 } catch (error) {
564 console.error(error);
565 sessionStorage.removeItem(storageKey);
566 }
567 }).toString();
568 if (props.nonce == null && remixContext?.nonce) props.nonce = remixContext.nonce;
569 return /* @__PURE__ */ React$1.createElement("script", {
570 ...props,
571 suppressHydrationWarning: true,
572 dangerouslySetInnerHTML: { __html: `(${restoreScroll})(${escapeHtml(JSON.stringify(storageKey || SCROLL_RESTORATION_STORAGE_KEY))}, ${escapeHtml(JSON.stringify(ssrKey))})` }
573 });
574}
575ScrollRestoration.displayName = "ScrollRestoration";
576/**
577* Handles the click behavior for router {@link Link | `<Link>`} components.This
578* is useful if you need to create custom {@link Link | `<Link>`} components with
579* the same click behavior we use in our exported {@link Link | `<Link>`}.
580*
581* @public
582* @category Hooks
583* @param to The URL to navigate to, can be a string or a partial {@link Path}.
584* @param options Options
585* @param options.preventScrollReset Whether to prevent the scroll position from
586* being reset to the top of the viewport on completion of the navigation when
587* using the {@link ScrollRestoration} component. Defaults to `false`.
588* @param options.relative The {@link RelativeRoutingType | relative routing type}
589* to use for the link. Defaults to `"route"`.
590* @param options.replace Whether to replace the current [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
591* entry instead of pushing a new one. Defaults to `false`.
592* @param options.state The state to add to the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
593* entry for this navigation. Defaults to `undefined`.
594* @param options.target The target attribute for the link. Defaults to `undefined`.
595* @param options.viewTransition Enables a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
596* for this navigation. To apply specific styles during the transition, see
597* {@link useViewTransitionState}. Defaults to `false`.
598* @param options.defaultShouldRevalidate Specify the default revalidation
599* behavior for the navigation. When not specified, loaders revalidate
600* according to the router's standard revalidation behavior.
601* @param options.mask Masked location to display in the browser instead
602* of the router location. Defaults to `undefined`.
603* @param options.useTransitions Wraps the navigation in
604* [`React.startTransition`](https://react.dev/reference/react/startTransition)
605* for concurrent rendering. Defaults to `false`.
606* @returns A click handler function that can be used in a custom {@link Link} component.
607*/
608function useLinkClickHandler(to, { target, replace: replaceProp, mask, state, preventScrollReset, relative, viewTransition, defaultShouldRevalidate, useTransitions } = {}) {
609 let navigate = useNavigate();
610 let location = useLocation();
611 let path = useResolvedPath(to, { relative });
612 return React$1.useCallback((event) => {
613 if (shouldProcessLinkClick(event, target)) {
614 event.preventDefault();
615 let replace = replaceProp !== void 0 ? replaceProp : createPath(location) === createPath(path);
616 let doNavigate = () => navigate(to, {
617 replace,
618 mask,
619 state,
620 preventScrollReset,
621 relative,
622 viewTransition,
623 defaultShouldRevalidate
624 });
625 if (useTransitions) React$1.startTransition(() => doNavigate());
626 else doNavigate();
627 }
628 }, [
629 location,
630 navigate,
631 path,
632 replaceProp,
633 mask,
634 state,
635 target,
636 to,
637 preventScrollReset,
638 relative,
639 viewTransition,
640 defaultShouldRevalidate,
641 useTransitions
642 ]);
643}
644/**
645* Returns a tuple of the current URL's [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
646* and a function to update them. Setting the search params causes a navigation.
647*
648* ```tsx
649* import { useSearchParams } from "react-router";
650*
651* export function SomeComponent() {
652* const [searchParams, setSearchParams] = useSearchParams();
653* // ...
654* }
655* ```
656*
657* ### `setSearchParams` function
658*
659* The second element of the tuple is a function that can be used to update the
660* search params. It accepts the same types as `defaultInit` and will cause a
661* navigation to the new URL.
662*
663* ```tsx
664* let [searchParams, setSearchParams] = useSearchParams();
665*
666* // a search param string
667* setSearchParams("?tab=1");
668*
669* // a shorthand object
670* setSearchParams({ tab: "1" });
671*
672* // object keys can be arrays for multiple values on the key
673* setSearchParams({ brand: ["nike", "reebok"] });
674*
675* // an array of tuples
676* setSearchParams([["tab", "1"]]);
677*
678* // a `URLSearchParams` object
679* setSearchParams(new URLSearchParams("?tab=1"));
680* ```
681*
682* It also supports a function callback like React's
683* [`setState`](https://react.dev/reference/react/useState#setstate):
684*
685* ```tsx
686* setSearchParams((searchParams) => {
687* searchParams.set("tab", "2");
688* return searchParams;
689* });
690* ```
691*
692* <docs-warning>The function callback version of `setSearchParams` does not support
693* the [queueing](https://react.dev/reference/react/useState#setstate-parameters)
694* logic that React's `setState` implements. Multiple calls to `setSearchParams`
695* in the same tick will not build on the prior value. If you need this behavior,
696* you can use `setState` manually.</docs-warning>
697*
698* ### Notes
699*
700* Note that `searchParams` is a stable reference, so you can reliably use it
701* as a dependency in React's [`useEffect`](https://react.dev/reference/react/useEffect)
702* hooks.
703*
704* ```tsx
705* useEffect(() => {
706* console.log(searchParams.get("tab"));
707* }, [searchParams]);
708* ```
709*
710* However, this also means it's mutable. If you change the object without
711* calling `setSearchParams`, its values will change between renders if some
712* other state causes the component to re-render and URL will not reflect the
713* values.
714*
715* @public
716* @category Hooks
717* @param defaultInit
718* You can initialize the search params with a default value, though it **will
719* not** change the URL on the first render.
720*
721* ```tsx
722* // a search param string
723* useSearchParams("?tab=1");
724*
725* // a shorthand object
726* useSearchParams({ tab: "1" });
727*
728* // object keys can be arrays for multiple values on the key
729* useSearchParams({ brand: ["nike", "reebok"] });
730*
731* // an array of tuples
732* useSearchParams([["tab", "1"]]);
733*
734* // a `URLSearchParams` object
735* useSearchParams(new URLSearchParams("?tab=1"));
736* ```
737* @returns A tuple of the current [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
738* and a function to update them.
739*/
740function useSearchParams(defaultInit) {
741 warning(typeof URLSearchParams !== "undefined", "You cannot use the `useSearchParams` hook in a browser that does not support the URLSearchParams API. If you need to support Internet Explorer 11, we recommend you load a polyfill such as https://github.com/ungap/url-search-params.");
742 let defaultSearchParamsRef = React$1.useRef(createSearchParams(defaultInit));
743 let hasSetSearchParamsRef = React$1.useRef(false);
744 let location = useLocation();
745 let searchParams = React$1.useMemo(() => getSearchParamsForLocation(location.search, hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current), [location.search]);
746 let navigate = useNavigate();
747 return [searchParams, React$1.useCallback((nextInit, navigateOptions) => {
748 const newSearchParams = createSearchParams(typeof nextInit === "function" ? nextInit(new URLSearchParams(searchParams)) : nextInit);
749 hasSetSearchParamsRef.current = true;
750 navigate("?" + newSearchParams, navigateOptions);
751 }, [navigate, searchParams])];
752}
753let fetcherId = 0;
754let getUniqueFetcherId = () => `__${String(++fetcherId)}__`;
755/**
756* The imperative version of {@link Form | `<Form>`} that lets you submit a form
757* from code instead of a user interaction.
758*
759* @example
760* import { useSubmit } from "react-router";
761*
762* function SomeComponent() {
763* const submit = useSubmit();
764* return (
765* <Form onChange={(event) => submit(event.currentTarget)} />
766* );
767* }
768*
769* @public
770* @category Hooks
771* @mode framework
772* @mode data
773* @returns A function that can be called to submit a {@link Form} imperatively.
774*/
775function useSubmit() {
776 let { router } = useDataRouterContext("useSubmit");
777 let { basename } = React$1.useContext(NavigationContext);
778 let currentRouteId = useCurrentRouteId("useSubmit");
779 let routerFetch = router.fetch;
780 let routerNavigate = router.navigate;
781 return React$1.useCallback(async (target, options = {}) => {
782 let { action, method, encType, formData, body } = getFormSubmissionInfo(target, basename);
783 if (options.navigate === false) await routerFetch(options.fetcherKey || getUniqueFetcherId(), currentRouteId, options.action || action, {
784 defaultShouldRevalidate: options.defaultShouldRevalidate,
785 preventScrollReset: options.preventScrollReset,
786 relative: options.relative,
787 formData,
788 body,
789 formMethod: options.method || method,
790 formEncType: options.encType || encType,
791 flushSync: options.flushSync
792 });
793 else await routerNavigate(options.action || action, {
794 defaultShouldRevalidate: options.defaultShouldRevalidate,
795 preventScrollReset: options.preventScrollReset,
796 relative: options.relative,
797 formData,
798 body,
799 formMethod: options.method || method,
800 formEncType: options.encType || encType,
801 replace: options.replace,
802 state: options.state,
803 fromRouteId: currentRouteId,
804 flushSync: options.flushSync,
805 viewTransition: options.viewTransition
806 });
807 }, [
808 routerFetch,
809 routerNavigate,
810 basename,
811 currentRouteId
812 ]);
813}
814/**
815* Resolves the URL to the closest route in the component hierarchy instead of
816* the current URL of the app.
817*
818* This is used internally by {@link Form} to resolve the `action` to the closest
819* route, but can be used generically as well.
820*
821* ```ts
822* import { useFormAction } from "react-router";
823*
824* function SomeComponent() {
825* // closest route URL
826* let action = useFormAction();
827*
828* // closest route URL + "destroy"
829* let destroyAction = useFormAction("destroy");
830* }
831* ```
832*
833* <docs-info>This hook adds a `basename` if your app specifies one, so that it
834* can be used with raw `<form>` elements in a progressively enhanced way. If
835* you are using this to provide an `action` to `<Form>` or `fetcher.submit`, you
836* will need to remove the `basename` since both of those will prepend it
837* internally.</docs-info>
838*
839*
840* @public
841* @category Hooks
842* @mode framework
843* @mode data
844* @param action The action to append to the closest route URL. Defaults to the
845* closest route URL.
846* @param options Options
847* @param options.relative The relative routing type to use when resolving the
848* action. Defaults to `"route"`.
849* @returns The resolved action URL.
850*/
851function useFormAction(action, { relative } = {}) {
852 let { basename } = React$1.useContext(NavigationContext);
853 let routeContext = React$1.useContext(RouteContext);
854 invariant(routeContext, "useFormAction must be used inside a RouteContext");
855 let [match] = routeContext.matches.slice(-1);
856 let path = { ...useResolvedPath(action ? action : ".", { relative }) };
857 let location = useLocation();
858 if (action == null) {
859 path.search = location.search;
860 let params = new URLSearchParams(path.search);
861 let indexValues = params.getAll("index");
862 if (indexValues.some((v) => v === "")) {
863 params.delete("index");
864 indexValues.filter((v) => v).forEach((v) => params.append("index", v));
865 let qs = params.toString();
866 path.search = qs ? `?${qs}` : "";
867 }
868 }
869 if ((!action || action === ".") && match.route.index) path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
870 if (basename !== "/") path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
871 return createPath(path);
872}
873/**
874* Useful for creating complex, dynamic user interfaces that require multiple,
875* concurrent data interactions without causing a navigation.
876*
877* Fetchers track their own, independent state and can be used to load data, submit
878* forms, and generally interact with [`action`](../../start/framework/route-module#action)
879* and [`loader`](../../start/framework/route-module#loader) functions.
880*
881* @example
882* import { useFetcher } from "react-router"
883*
884* function SomeComponent() {
885* let fetcher = useFetcher()
886*
887* // states are available on the fetcher
888* fetcher.state // "idle" | "loading" | "submitting"
889* fetcher.data // the data returned from the action or loader
890*
891* // render a form
892* <fetcher.Form method="post" />
893*
894* // load data
895* fetcher.load("/some/route")
896*
897* // submit data
898* fetcher.submit(someFormRef, { method: "post" })
899* fetcher.submit(someData, {
900* method: "post",
901* encType: "application/json"
902* })
903*
904* // reset fetcher
905* fetcher.reset()
906* }
907*
908* @public
909* @category Hooks
910* @mode framework
911* @mode data
912* @param options Options
913* @param options.key A unique key to identify the fetcher.
914*
915*
916* By default, `useFetcher` generates a unique fetcher scoped to that component.
917* If you want to identify a fetcher with your own key such that you can access
918* it from elsewhere in your app, you can do that with the `key` option:
919*
920* ```tsx
921* function SomeComp() {
922* let fetcher = useFetcher({ key: "my-key" })
923* // ...
924* }
925*
926* // Somewhere else
927* function AnotherComp() {
928* // this will be the same fetcher, sharing the state across the app
929* let fetcher = useFetcher({ key: "my-key" });
930* // ...
931* }
932* ```
933* @returns A {@link FetcherWithComponents} object that contains the fetcher's state, data, and components for submitting forms and loading data.
934*/
935function useFetcher({ key } = {}) {
936 let { router } = useDataRouterContext("useFetcher");
937 let fetchersContext = useDataRouterFetchers("useFetcher");
938 let routeId = useCurrentRouteId("useFetcher");
939 let defaultKey = React$1.useId();
940 let [fetcherKey, setFetcherKey] = React$1.useState(key || defaultKey);
941 if (key && key !== fetcherKey) setFetcherKey(key);
942 let { deleteFetcher, getFetcher, resetFetcher, fetch: routerFetch } = router;
943 React$1.useEffect(() => {
944 getFetcher(fetcherKey);
945 return () => deleteFetcher(fetcherKey);
946 }, [
947 deleteFetcher,
948 getFetcher,
949 fetcherKey
950 ]);
951 let load = React$1.useCallback(async (href, opts) => {
952 invariant(routeId, "No routeId available for fetcher.load()");
953 await routerFetch(fetcherKey, routeId, href, opts);
954 }, [
955 fetcherKey,
956 routeId,
957 routerFetch
958 ]);
959 let submitImpl = useSubmit();
960 let submit = React$1.useCallback(async (target, opts) => {
961 await submitImpl(target, {
962 ...opts,
963 navigate: false,
964 fetcherKey
965 });
966 }, [fetcherKey, submitImpl]);
967 let reset = React$1.useCallback((opts) => resetFetcher(fetcherKey, opts), [resetFetcher, fetcherKey]);
968 let FetcherForm = React$1.useMemo(() => {
969 let FetcherForm = React$1.forwardRef((props, ref) => {
970 return /* @__PURE__ */ React$1.createElement(Form, {
971 ...props,
972 navigate: false,
973 fetcherKey,
974 ref
975 });
976 });
977 FetcherForm.displayName = "fetcher.Form";
978 return FetcherForm;
979 }, [fetcherKey]);
980 let fetcher = fetchersContext.fetchers.get(fetcherKey) || IDLE_FETCHER;
981 let data = fetchersContext.fetcherData.get(fetcherKey);
982 return React$1.useMemo(() => ({
983 Form: FetcherForm,
984 submit,
985 load,
986 reset,
987 ...fetcher,
988 data
989 }), [
990 FetcherForm,
991 submit,
992 load,
993 reset,
994 fetcher,
995 data
996 ]);
997}
998/**
999* Returns an array of all in-flight {@link Fetcher}s. This is useful for components
1000* throughout the app that didn't create the fetchers but want to use their submissions
1001* to participate in optimistic UI.
1002*
1003* @example
1004* import { useFetchers } from "react-router";
1005*
1006* function SomeComponent() {
1007* const fetchers = useFetchers();
1008* fetchers[0].formData; // FormData
1009* fetchers[0].state; // etc.
1010* // ...
1011* }
1012*
1013* @public
1014* @category Hooks
1015* @mode framework
1016* @mode data
1017* @returns An array of all in-flight {@link Fetcher}s, each with a unique `key`
1018* property.
1019*/
1020function useFetchers() {
1021 let { fetchers } = useDataRouterFetchers("useFetchers");
1022 return React$1.useMemo(() => Array.from(fetchers.entries()).map(([key, fetcher]) => ({
1023 ...fetcher,
1024 key
1025 })), [fetchers]);
1026}
1027const SCROLL_RESTORATION_STORAGE_KEY = "react-router-scroll-positions";
1028let savedScrollPositions = {};
1029function getScrollRestorationKey(location, matches, basename, getKey) {
1030 let key = null;
1031 if (getKey) if (basename !== "/") key = getKey({
1032 ...location,
1033 pathname: stripBasename(location.pathname, basename) || location.pathname
1034 }, matches);
1035 else key = getKey(location, matches);
1036 if (key == null) key = location.key;
1037 return key;
1038}
1039/**
1040* When rendered inside a {@link RouterProvider}, will restore scroll positions
1041* on navigations
1042*
1043* <!--
1044* Not marked `@public` because we only export as UNSAFE_ and therefore we don't
1045* maintain an .md file for this hook
1046* -->
1047*
1048* @name UNSAFE_useScrollRestoration
1049* @category Hooks
1050* @mode framework
1051* @mode data
1052* @param options Options
1053* @param options.getKey A function that returns a key to use for scroll restoration.
1054* This is useful for custom scroll restoration logic, such as using only the pathname
1055* so that subsequent navigations to prior paths will restore the scroll. Defaults
1056* to `location.key`.
1057* @param options.storageKey The key to use for storing scroll positions in
1058* `sessionStorage`. Defaults to `"react-router-scroll-positions"`.
1059* @returns {void}
1060*/
1061function useScrollRestoration({ getKey, storageKey } = {}) {
1062 let { router } = useDataRouterContext("useScrollRestoration");
1063 let { restoreScrollPosition, preventScrollReset } = useDataRouterState("useScrollRestoration");
1064 let { basename } = React$1.useContext(NavigationContext);
1065 let location = useLocation();
1066 let matches = useMatches();
1067 let navigation = useNavigation();
1068 React$1.useEffect(() => {
1069 window.history.scrollRestoration = "manual";
1070 return () => {
1071 window.history.scrollRestoration = "auto";
1072 };
1073 }, []);
1074 usePageShow(React$1.useCallback((event) => {
1075 if (event.persisted) window.history.scrollRestoration = "manual";
1076 }, []));
1077 usePageHide(React$1.useCallback(() => {
1078 if (navigation.state === "idle") {
1079 let key = getScrollRestorationKey(location, matches, basename, getKey);
1080 savedScrollPositions[key] = window.scrollY;
1081 }
1082 try {
1083 sessionStorage.setItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY, JSON.stringify(savedScrollPositions));
1084 } catch (error) {
1085 warning(false, `Failed to save scroll positions in sessionStorage, <ScrollRestoration /> will not work properly (${error}).`);
1086 }
1087 window.history.scrollRestoration = "auto";
1088 }, [
1089 navigation.state,
1090 getKey,
1091 basename,
1092 location,
1093 matches,
1094 storageKey
1095 ]));
1096 if (typeof document !== "undefined") {
1097 React$1.useLayoutEffect(() => {
1098 try {
1099 let sessionPositions = sessionStorage.getItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY);
1100 if (sessionPositions) savedScrollPositions = JSON.parse(sessionPositions);
1101 } catch {}
1102 }, [storageKey]);
1103 React$1.useLayoutEffect(() => {
1104 let disableScrollRestoration = router?.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKey ? (location, matches) => getScrollRestorationKey(location, matches, basename, getKey) : void 0);
1105 return () => disableScrollRestoration && disableScrollRestoration();
1106 }, [
1107 router,
1108 basename,
1109 getKey
1110 ]);
1111 React$1.useLayoutEffect(() => {
1112 if (restoreScrollPosition === false) return;
1113 if (typeof restoreScrollPosition === "number") {
1114 window.scrollTo(0, restoreScrollPosition);
1115 return;
1116 }
1117 try {
1118 if (location.hash) {
1119 let el = document.getElementById(decodeURIComponent(location.hash.slice(1)));
1120 if (el) {
1121 el.scrollIntoView();
1122 return;
1123 }
1124 }
1125 } catch {
1126 warning(false, `"${location.hash.slice(1)}" is not a decodable element ID. The view will not scroll to it.`);
1127 }
1128 if (preventScrollReset === true) return;
1129 window.scrollTo(0, 0);
1130 }, [
1131 location,
1132 restoreScrollPosition,
1133 preventScrollReset
1134 ]);
1135 }
1136}
1137/**
1138* Set up a callback to be fired on [Window's `beforeunload` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event).
1139*
1140* @public
1141* @category Hooks
1142* @param callback The callback to be called when the [`beforeunload` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event)
1143* is fired.
1144* @param options Options
1145* @param options.capture If `true`, the event will be captured during the capture
1146* phase. Defaults to `false`.
1147* @returns {void}
1148*/
1149function useBeforeUnload(callback, options) {
1150 let { capture } = options || {};
1151 React$1.useEffect(() => {
1152 let opts = capture != null ? { capture } : void 0;
1153 window.addEventListener("beforeunload", callback, opts);
1154 return () => {
1155 window.removeEventListener("beforeunload", callback, opts);
1156 };
1157 }, [callback, capture]);
1158}
1159function usePageHide(callback, options) {
1160 let { capture } = options || {};
1161 React$1.useEffect(() => {
1162 let opts = capture != null ? { capture } : void 0;
1163 window.addEventListener("pagehide", callback, opts);
1164 return () => {
1165 window.removeEventListener("pagehide", callback, opts);
1166 };
1167 }, [callback, capture]);
1168}
1169function usePageShow(callback, options) {
1170 let { capture } = options || {};
1171 React$1.useEffect(() => {
1172 let opts = capture != null ? { capture } : void 0;
1173 window.addEventListener("pageshow", callback, opts);
1174 return () => {
1175 window.removeEventListener("pageshow", callback, opts);
1176 };
1177 }, [callback, capture]);
1178}
1179/**
1180* Wrapper around {@link useBlocker} to show a [`window.confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
1181* prompt to users instead of building a custom UI with {@link useBlocker}.
1182*
1183* The `unstable_` flag will not be removed because this technique has a lot of
1184* rough edges and behaves very differently (and incorrectly sometimes) across
1185* browsers if users click addition back/forward navigations while the
1186* confirmation is open. Use at your own risk.
1187*
1188* @example
1189* function ImportantForm() {
1190* let [value, setValue] = React.useState("");
1191*
1192* // Block navigating elsewhere when data has been entered into the input
1193* unstable_usePrompt({
1194* message: "Are you sure?",
1195* when: ({ currentLocation, nextLocation }) =>
1196* value !== "" &&
1197* currentLocation.pathname !== nextLocation.pathname,
1198* });
1199*
1200* return (
1201* <Form method="post">
1202* <label>
1203* Enter some important data:
1204* <input
1205* name="data"
1206* value={value}
1207* onChange={(e) => setValue(e.target.value)}
1208* />
1209* </label>
1210* <button type="submit">Save</button>
1211* </Form>
1212* );
1213* }
1214*
1215* @name unstable_usePrompt
1216* @public
1217* @category Hooks
1218* @mode framework
1219* @mode data
1220* @param options Options
1221* @param options.message The message to show in the confirmation dialog.
1222* @param options.when A boolean or a function that returns a boolean indicating
1223* whether to block the navigation. If a function is provided, it will receive an
1224* object with `currentLocation` and `nextLocation` properties.
1225* @returns {void}
1226*/
1227function usePrompt({ when, message }) {
1228 let blocker = useBlocker(when);
1229 React$1.useEffect(() => {
1230 if (blocker.state === "blocked") if (window.confirm(message)) setTimeout(blocker.proceed, 0);
1231 else blocker.reset();
1232 }, [blocker, message]);
1233 React$1.useEffect(() => {
1234 if (blocker.state === "blocked" && !when) blocker.reset();
1235 }, [blocker, when]);
1236}
1237/**
1238* This hook returns `true` when there is an active [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
1239* and the specified location matches either side of the navigation (the URL you are
1240* navigating **to** or the URL you are navigating **from**). This can be used to apply finer-grained styles to
1241* elements to further customize the view transition. This requires that view
1242* transitions have been enabled for the given navigation via {@link LinkProps.viewTransition}
1243* (or the `Form`, `submit`, or `navigate` call)
1244*
1245* @public
1246* @category Hooks
1247* @mode framework
1248* @mode data
1249* @param to The {@link To} location to compare against the active transition's current
1250* and next URLs.
1251* @param options Options
1252* @param options.relative The relative routing type to use when resolving the
1253* `to` location, defaults to `"route"`. See {@link RelativeRoutingType} for
1254* more details.
1255* @returns `true` if there is an active [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
1256* and the resolved path matches the transition's destination or source pathname, otherwise `false`.
1257*/
1258function useViewTransitionState(to, { relative } = {}) {
1259 let vtContext = React$1.useContext(ViewTransitionContext);
1260 invariant(vtContext != null, "`useViewTransitionState` must be used within `react-router/dom`'s `RouterProvider`. Did you accidentally import `RouterProvider` from `react-router`?");
1261 let { basename } = useDataRouterContext("useViewTransitionState");
1262 let path = useResolvedPath(to, { relative });
1263 if (!vtContext.isTransitioning) return false;
1264 let currentPath = stripBasename(vtContext.currentLocation.pathname, basename) || vtContext.currentLocation.pathname;
1265 let nextPath = stripBasename(vtContext.nextLocation.pathname, basename) || vtContext.nextLocation.pathname;
1266 return matchPath(path.pathname, nextPath) != null || matchPath(path.pathname, currentPath) != null;
1267}
1268//#endregion
1269export { BrowserRouter, Form, HashRouter, HistoryRouter, Link, NavLink, ScrollRestoration, createBrowserRouter, createHashRouter, useBeforeUnload, useFetcher, useFetchers, useFormAction, useLinkClickHandler, usePrompt, useScrollRestoration, useSearchParams, useSubmit, useViewTransitionState };