source: node_modules/react-router-dom/dist/react-router-dom.development.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 52.1 KB
Line 
1/**
2 * React Router DOM v6.22.1
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 * as React from 'react';
12import * as ReactDOM from 'react-dom';
13import { UNSAFE_mapRouteProperties, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, Router, UNSAFE_useRoutesImpl, UNSAFE_NavigationContext, useHref, useResolvedPath, useLocation, useNavigate, createPath, UNSAFE_useRouteId, UNSAFE_RouteContext, useMatches, useNavigation, useBlocker } from 'react-router';
14export { AbortedDeferredError, Await, MemoryRouter, Navigate, NavigationType, Outlet, Route, Router, Routes, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, UNSAFE_LocationContext, UNSAFE_NavigationContext, UNSAFE_RouteContext, UNSAFE_useRouteId, createMemoryRouter, createPath, createRoutesFromChildren, createRoutesFromElements, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, redirectDocument, renderMatches, resolvePath, useActionData, useAsyncError, useAsyncValue, useBlocker, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes } from 'react-router';
15import { stripBasename, UNSAFE_warning, createRouter, createBrowserHistory, createHashHistory, UNSAFE_ErrorResponseImpl, UNSAFE_invariant, joinPaths, IDLE_FETCHER, matchPath } from '@remix-run/router';
16
17const defaultMethod = "get";
18const defaultEncType = "application/x-www-form-urlencoded";
19function isHtmlElement(object) {
20 return object != null && typeof object.tagName === "string";
21}
22function isButtonElement(object) {
23 return isHtmlElement(object) && object.tagName.toLowerCase() === "button";
24}
25function isFormElement(object) {
26 return isHtmlElement(object) && object.tagName.toLowerCase() === "form";
27}
28function isInputElement(object) {
29 return isHtmlElement(object) && object.tagName.toLowerCase() === "input";
30}
31function isModifiedEvent(event) {
32 return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
33}
34function shouldProcessLinkClick(event, target) {
35 return event.button === 0 && (
36 // Ignore everything but left clicks
37 !target || target === "_self") &&
38 // Let browser handle "target=_blank" etc.
39 !isModifiedEvent(event) // Ignore clicks with modifier keys
40 ;
41}
42
43/**
44 * Creates a URLSearchParams object using the given initializer.
45 *
46 * This is identical to `new URLSearchParams(init)` except it also
47 * supports arrays as values in the object form of the initializer
48 * instead of just strings. This is convenient when you need multiple
49 * values for a given key, but don't want to use an array initializer.
50 *
51 * For example, instead of:
52 *
53 * let searchParams = new URLSearchParams([
54 * ['sort', 'name'],
55 * ['sort', 'price']
56 * ]);
57 *
58 * you can do:
59 *
60 * let searchParams = createSearchParams({
61 * sort: ['name', 'price']
62 * });
63 */
64function createSearchParams(init = "") {
65 return new URLSearchParams(typeof init === "string" || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).reduce((memo, key) => {
66 let value = init[key];
67 return memo.concat(Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]]);
68 }, []));
69}
70function getSearchParamsForLocation(locationSearch, defaultSearchParams) {
71 let searchParams = createSearchParams(locationSearch);
72 if (defaultSearchParams) {
73 // Use `defaultSearchParams.forEach(...)` here instead of iterating of
74 // `defaultSearchParams.keys()` to work-around a bug in Firefox related to
75 // web extensions. Relevant Bugzilla tickets:
76 // https://bugzilla.mozilla.org/show_bug.cgi?id=1414602
77 // https://bugzilla.mozilla.org/show_bug.cgi?id=1023984
78 defaultSearchParams.forEach((_, key) => {
79 if (!searchParams.has(key)) {
80 defaultSearchParams.getAll(key).forEach(value => {
81 searchParams.append(key, value);
82 });
83 }
84 });
85 }
86 return searchParams;
87}
88
89// Thanks https://github.com/sindresorhus/type-fest!
90
91// One-time check for submitter support
92let _formDataSupportsSubmitter = null;
93function isFormDataSubmitterSupported() {
94 if (_formDataSupportsSubmitter === null) {
95 try {
96 new FormData(document.createElement("form"),
97 // @ts-expect-error if FormData supports the submitter parameter, this will throw
98 0);
99 _formDataSupportsSubmitter = false;
100 } catch (e) {
101 _formDataSupportsSubmitter = true;
102 }
103 }
104 return _formDataSupportsSubmitter;
105}
106const supportedFormEncTypes = new Set(["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]);
107function getFormEncType(encType) {
108 if (encType != null && !supportedFormEncTypes.has(encType)) {
109 UNSAFE_warning(false, `"${encType}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` ` + `and will default to "${defaultEncType}"`) ;
110 return null;
111 }
112 return encType;
113}
114function getFormSubmissionInfo(target, basename) {
115 let method;
116 let action;
117 let encType;
118 let formData;
119 let body;
120 if (isFormElement(target)) {
121 // When grabbing the action from the element, it will have had the basename
122 // prefixed to ensure non-JS scenarios work, so strip it since we'll
123 // re-prefix in the router
124 let attr = target.getAttribute("action");
125 action = attr ? stripBasename(attr, basename) : null;
126 method = target.getAttribute("method") || defaultMethod;
127 encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
128 formData = new FormData(target);
129 } else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
130 let form = target.form;
131 if (form == null) {
132 throw new Error(`Cannot submit a <button> or <input type="submit"> without a <form>`);
133 }
134
135 // <button>/<input type="submit"> may override attributes of <form>
136
137 // When grabbing the action from the element, it will have had the basename
138 // prefixed to ensure non-JS scenarios work, so strip it since we'll
139 // re-prefix in the router
140 let attr = target.getAttribute("formaction") || form.getAttribute("action");
141 action = attr ? stripBasename(attr, basename) : null;
142 method = target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
143 encType = getFormEncType(target.getAttribute("formenctype")) || getFormEncType(form.getAttribute("enctype")) || defaultEncType;
144
145 // Build a FormData object populated from a form and submitter
146 formData = new FormData(form, target);
147
148 // If this browser doesn't support the `FormData(el, submitter)` format,
149 // then tack on the submitter value at the end. This is a lightweight
150 // solution that is not 100% spec compliant. For complete support in older
151 // browsers, consider using the `formdata-submitter-polyfill` package
152 if (!isFormDataSubmitterSupported()) {
153 let {
154 name,
155 type,
156 value
157 } = target;
158 if (type === "image") {
159 let prefix = name ? `${name}.` : "";
160 formData.append(`${prefix}x`, "0");
161 formData.append(`${prefix}y`, "0");
162 } else if (name) {
163 formData.append(name, value);
164 }
165 }
166 } else if (isHtmlElement(target)) {
167 throw new Error(`Cannot submit element that is not <form>, <button>, or ` + `<input type="submit|image">`);
168 } else {
169 method = defaultMethod;
170 action = null;
171 encType = defaultEncType;
172 body = target;
173 }
174
175 // Send body for <Form encType="text/plain" so we encode it into text
176 if (formData && encType === "text/plain") {
177 body = formData;
178 formData = undefined;
179 }
180 return {
181 action,
182 method: method.toLowerCase(),
183 encType,
184 formData,
185 body
186 };
187}
188
189/**
190 * NOTE: If you refactor this to split up the modules into separate files,
191 * you'll need to update the rollup config for react-router-dom-v5-compat.
192 */
193//#endregion
194// HEY YOU! DON'T TOUCH THIS VARIABLE!
195//
196// It is replaced with the proper version at build time via a babel plugin in
197// the rollup config.
198//
199// Export a global property onto the window for React Router detection by the
200// Core Web Vitals Technology Report. This way they can configure the `wappalyzer`
201// to detect and properly classify live websites as being built with React Router:
202// https://github.com/HTTPArchive/wappalyzer/blob/main/src/technologies/r.json
203const REACT_ROUTER_VERSION = "6";
204try {
205 window.__reactRouterVersion = REACT_ROUTER_VERSION;
206} catch (e) {
207 // no-op
208}
209
210////////////////////////////////////////////////////////////////////////////////
211//#region Routers
212////////////////////////////////////////////////////////////////////////////////
213function createBrowserRouter(routes, opts) {
214 return createRouter({
215 basename: opts?.basename,
216 future: {
217 ...opts?.future,
218 v7_prependBasename: true
219 },
220 history: createBrowserHistory({
221 window: opts?.window
222 }),
223 hydrationData: opts?.hydrationData || parseHydrationData(),
224 routes,
225 mapRouteProperties: UNSAFE_mapRouteProperties,
226 window: opts?.window
227 }).initialize();
228}
229function createHashRouter(routes, opts) {
230 return createRouter({
231 basename: opts?.basename,
232 future: {
233 ...opts?.future,
234 v7_prependBasename: true
235 },
236 history: createHashHistory({
237 window: opts?.window
238 }),
239 hydrationData: opts?.hydrationData || parseHydrationData(),
240 routes,
241 mapRouteProperties: UNSAFE_mapRouteProperties,
242 window: opts?.window
243 }).initialize();
244}
245function parseHydrationData() {
246 let state = window?.__staticRouterHydrationData;
247 if (state && state.errors) {
248 state = {
249 ...state,
250 errors: deserializeErrors(state.errors)
251 };
252 }
253 return state;
254}
255function deserializeErrors(errors) {
256 if (!errors) return null;
257 let entries = Object.entries(errors);
258 let serialized = {};
259 for (let [key, val] of entries) {
260 // Hey you! If you change this, please change the corresponding logic in
261 // serializeErrors in react-router-dom/server.tsx :)
262 if (val && val.__type === "RouteErrorResponse") {
263 serialized[key] = new UNSAFE_ErrorResponseImpl(val.status, val.statusText, val.data, val.internal === true);
264 } else if (val && val.__type === "Error") {
265 // Attempt to reconstruct the right type of Error (i.e., ReferenceError)
266 if (val.__subType) {
267 let ErrorConstructor = window[val.__subType];
268 if (typeof ErrorConstructor === "function") {
269 try {
270 // @ts-expect-error
271 let error = new ErrorConstructor(val.message);
272 // Wipe away the client-side stack trace. Nothing to fill it in with
273 // because we don't serialize SSR stack traces for security reasons
274 error.stack = "";
275 serialized[key] = error;
276 } catch (e) {
277 // no-op - fall through and create a normal Error
278 }
279 }
280 }
281 if (serialized[key] == null) {
282 let error = new Error(val.message);
283 // Wipe away the client-side stack trace. Nothing to fill it in with
284 // because we don't serialize SSR stack traces for security reasons
285 error.stack = "";
286 serialized[key] = error;
287 }
288 } else {
289 serialized[key] = val;
290 }
291 }
292 return serialized;
293}
294
295//#endregion
296
297////////////////////////////////////////////////////////////////////////////////
298//#region Contexts
299////////////////////////////////////////////////////////////////////////////////
300const ViewTransitionContext = /*#__PURE__*/React.createContext({
301 isTransitioning: false
302});
303{
304 ViewTransitionContext.displayName = "ViewTransition";
305}
306
307// TODO: (v7) Change the useFetcher data from `any` to `unknown`
308
309const FetchersContext = /*#__PURE__*/React.createContext(new Map());
310{
311 FetchersContext.displayName = "Fetchers";
312}
313
314//#endregion
315
316////////////////////////////////////////////////////////////////////////////////
317//#region Components
318////////////////////////////////////////////////////////////////////////////////
319
320/**
321 Webpack + React 17 fails to compile on any of the following because webpack
322 complains that `startTransition` doesn't exist in `React`:
323 * import { startTransition } from "react"
324 * import * as React from from "react";
325 "startTransition" in React ? React.startTransition(() => setState()) : setState()
326 * import * as React from from "react";
327 "startTransition" in React ? React["startTransition"](() => setState()) : setState()
328
329 Moving it to a constant such as the following solves the Webpack/React 17 issue:
330 * import * as React from from "react";
331 const START_TRANSITION = "startTransition";
332 START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
333
334 However, that introduces webpack/terser minification issues in production builds
335 in React 18 where minification/obfuscation ends up removing the call of
336 React.startTransition entirely from the first half of the ternary. Grabbing
337 this exported reference once up front resolves that issue.
338
339 See https://github.com/remix-run/react-router/issues/10579
340*/
341const START_TRANSITION = "startTransition";
342const startTransitionImpl = React[START_TRANSITION];
343const FLUSH_SYNC = "flushSync";
344const flushSyncImpl = ReactDOM[FLUSH_SYNC];
345const USE_ID = "useId";
346const useIdImpl = React[USE_ID];
347function startTransitionSafe(cb) {
348 if (startTransitionImpl) {
349 startTransitionImpl(cb);
350 } else {
351 cb();
352 }
353}
354function flushSyncSafe(cb) {
355 if (flushSyncImpl) {
356 flushSyncImpl(cb);
357 } else {
358 cb();
359 }
360}
361class Deferred {
362 status = "pending";
363
364 // @ts-expect-error - no initializer
365
366 // @ts-expect-error - no initializer
367
368 constructor() {
369 this.promise = new Promise((resolve, reject) => {
370 this.resolve = value => {
371 if (this.status === "pending") {
372 this.status = "resolved";
373 resolve(value);
374 }
375 };
376 this.reject = reason => {
377 if (this.status === "pending") {
378 this.status = "rejected";
379 reject(reason);
380 }
381 };
382 });
383 }
384}
385
386/**
387 * Given a Remix Router instance, render the appropriate UI
388 */
389function RouterProvider({
390 fallbackElement,
391 router,
392 future
393}) {
394 let [state, setStateImpl] = React.useState(router.state);
395 let [pendingState, setPendingState] = React.useState();
396 let [vtContext, setVtContext] = React.useState({
397 isTransitioning: false
398 });
399 let [renderDfd, setRenderDfd] = React.useState();
400 let [transition, setTransition] = React.useState();
401 let [interruption, setInterruption] = React.useState();
402 let fetcherData = React.useRef(new Map());
403 let {
404 v7_startTransition
405 } = future || {};
406 let optInStartTransition = React.useCallback(cb => {
407 if (v7_startTransition) {
408 startTransitionSafe(cb);
409 } else {
410 cb();
411 }
412 }, [v7_startTransition]);
413 let setState = React.useCallback((newState, {
414 deletedFetchers,
415 unstable_flushSync: flushSync,
416 unstable_viewTransitionOpts: viewTransitionOpts
417 }) => {
418 deletedFetchers.forEach(key => fetcherData.current.delete(key));
419 newState.fetchers.forEach((fetcher, key) => {
420 if (fetcher.data !== undefined) {
421 fetcherData.current.set(key, fetcher.data);
422 }
423 });
424 let isViewTransitionUnavailable = router.window == null || typeof router.window.document.startViewTransition !== "function";
425
426 // If this isn't a view transition or it's not available in this browser,
427 // just update and be done with it
428 if (!viewTransitionOpts || isViewTransitionUnavailable) {
429 if (flushSync) {
430 flushSyncSafe(() => setStateImpl(newState));
431 } else {
432 optInStartTransition(() => setStateImpl(newState));
433 }
434 return;
435 }
436
437 // flushSync + startViewTransition
438 if (flushSync) {
439 // Flush through the context to mark DOM elements as transition=ing
440 flushSyncSafe(() => {
441 // Cancel any pending transitions
442 if (transition) {
443 renderDfd && renderDfd.resolve();
444 transition.skipTransition();
445 }
446 setVtContext({
447 isTransitioning: true,
448 flushSync: true,
449 currentLocation: viewTransitionOpts.currentLocation,
450 nextLocation: viewTransitionOpts.nextLocation
451 });
452 });
453
454 // Update the DOM
455 let t = router.window.document.startViewTransition(() => {
456 flushSyncSafe(() => setStateImpl(newState));
457 });
458
459 // Clean up after the animation completes
460 t.finished.finally(() => {
461 flushSyncSafe(() => {
462 setRenderDfd(undefined);
463 setTransition(undefined);
464 setPendingState(undefined);
465 setVtContext({
466 isTransitioning: false
467 });
468 });
469 });
470 flushSyncSafe(() => setTransition(t));
471 return;
472 }
473
474 // startTransition + startViewTransition
475 if (transition) {
476 // Interrupting an in-progress transition, cancel and let everything flush
477 // out, and then kick off a new transition from the interruption state
478 renderDfd && renderDfd.resolve();
479 transition.skipTransition();
480 setInterruption({
481 state: newState,
482 currentLocation: viewTransitionOpts.currentLocation,
483 nextLocation: viewTransitionOpts.nextLocation
484 });
485 } else {
486 // Completed navigation update with opted-in view transitions, let 'er rip
487 setPendingState(newState);
488 setVtContext({
489 isTransitioning: true,
490 flushSync: false,
491 currentLocation: viewTransitionOpts.currentLocation,
492 nextLocation: viewTransitionOpts.nextLocation
493 });
494 }
495 }, [router.window, transition, renderDfd, fetcherData, optInStartTransition]);
496
497 // Need to use a layout effect here so we are subscribed early enough to
498 // pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
499 React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
500
501 // When we start a view transition, create a Deferred we can use for the
502 // eventual "completed" render
503 React.useEffect(() => {
504 if (vtContext.isTransitioning && !vtContext.flushSync) {
505 setRenderDfd(new Deferred());
506 }
507 }, [vtContext]);
508
509 // Once the deferred is created, kick off startViewTransition() to update the
510 // DOM and then wait on the Deferred to resolve (indicating the DOM update has
511 // happened)
512 React.useEffect(() => {
513 if (renderDfd && pendingState && router.window) {
514 let newState = pendingState;
515 let renderPromise = renderDfd.promise;
516 let transition = router.window.document.startViewTransition(async () => {
517 optInStartTransition(() => setStateImpl(newState));
518 await renderPromise;
519 });
520 transition.finished.finally(() => {
521 setRenderDfd(undefined);
522 setTransition(undefined);
523 setPendingState(undefined);
524 setVtContext({
525 isTransitioning: false
526 });
527 });
528 setTransition(transition);
529 }
530 }, [optInStartTransition, pendingState, renderDfd, router.window]);
531
532 // When the new location finally renders and is committed to the DOM, this
533 // effect will run to resolve the transition
534 React.useEffect(() => {
535 if (renderDfd && pendingState && state.location.key === pendingState.location.key) {
536 renderDfd.resolve();
537 }
538 }, [renderDfd, transition, state.location, pendingState]);
539
540 // If we get interrupted with a new navigation during a transition, we skip
541 // the active transition, let it cleanup, then kick it off again here
542 React.useEffect(() => {
543 if (!vtContext.isTransitioning && interruption) {
544 setPendingState(interruption.state);
545 setVtContext({
546 isTransitioning: true,
547 flushSync: false,
548 currentLocation: interruption.currentLocation,
549 nextLocation: interruption.nextLocation
550 });
551 setInterruption(undefined);
552 }
553 }, [vtContext.isTransitioning, interruption]);
554 React.useEffect(() => {
555 UNSAFE_warning(fallbackElement == null || !router.future.v7_partialHydration, "`<RouterProvider fallbackElement>` is deprecated when using " + "`v7_partialHydration`, use a `HydrateFallback` component instead") ;
556 // Only log this once on initial mount
557 // eslint-disable-next-line react-hooks/exhaustive-deps
558 }, []);
559 let navigator = React.useMemo(() => {
560 return {
561 createHref: router.createHref,
562 encodeLocation: router.encodeLocation,
563 go: n => router.navigate(n),
564 push: (to, state, opts) => router.navigate(to, {
565 state,
566 preventScrollReset: opts?.preventScrollReset
567 }),
568 replace: (to, state, opts) => router.navigate(to, {
569 replace: true,
570 state,
571 preventScrollReset: opts?.preventScrollReset
572 })
573 };
574 }, [router]);
575 let basename = router.basename || "/";
576 let dataRouterContext = React.useMemo(() => ({
577 router,
578 navigator,
579 static: false,
580 basename
581 }), [router, navigator, basename]);
582
583 // The fragment and {null} here are important! We need them to keep React 18's
584 // useId happy when we are server-rendering since we may have a <script> here
585 // containing the hydrated server-side staticContext (from StaticRouterProvider).
586 // useId relies on the component tree structure to generate deterministic id's
587 // so we need to ensure it remains the same on the client even though
588 // we don't need the <script> tag
589 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UNSAFE_DataRouterContext.Provider, {
590 value: dataRouterContext
591 }, /*#__PURE__*/React.createElement(UNSAFE_DataRouterStateContext.Provider, {
592 value: state
593 }, /*#__PURE__*/React.createElement(FetchersContext.Provider, {
594 value: fetcherData.current
595 }, /*#__PURE__*/React.createElement(ViewTransitionContext.Provider, {
596 value: vtContext
597 }, /*#__PURE__*/React.createElement(Router, {
598 basename: basename,
599 location: state.location,
600 navigationType: state.historyAction,
601 navigator: navigator,
602 future: {
603 v7_relativeSplatPath: router.future.v7_relativeSplatPath
604 }
605 }, state.initialized || router.future.v7_partialHydration ? /*#__PURE__*/React.createElement(DataRoutes, {
606 routes: router.routes,
607 future: router.future,
608 state: state
609 }) : fallbackElement))))), null);
610}
611function DataRoutes({
612 routes,
613 future,
614 state
615}) {
616 return UNSAFE_useRoutesImpl(routes, undefined, state, future);
617}
618/**
619 * A `<Router>` for use in web browsers. Provides the cleanest URLs.
620 */
621function BrowserRouter({
622 basename,
623 children,
624 future,
625 window
626}) {
627 let historyRef = React.useRef();
628 if (historyRef.current == null) {
629 historyRef.current = createBrowserHistory({
630 window,
631 v5Compat: true
632 });
633 }
634 let history = historyRef.current;
635 let [state, setStateImpl] = React.useState({
636 action: history.action,
637 location: history.location
638 });
639 let {
640 v7_startTransition
641 } = future || {};
642 let setState = React.useCallback(newState => {
643 v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
644 }, [setStateImpl, v7_startTransition]);
645 React.useLayoutEffect(() => history.listen(setState), [history, setState]);
646 return /*#__PURE__*/React.createElement(Router, {
647 basename: basename,
648 children: children,
649 location: state.location,
650 navigationType: state.action,
651 navigator: history,
652 future: future
653 });
654}
655/**
656 * A `<Router>` for use in web browsers. Stores the location in the hash
657 * portion of the URL so it is not sent to the server.
658 */
659function HashRouter({
660 basename,
661 children,
662 future,
663 window
664}) {
665 let historyRef = React.useRef();
666 if (historyRef.current == null) {
667 historyRef.current = createHashHistory({
668 window,
669 v5Compat: true
670 });
671 }
672 let history = historyRef.current;
673 let [state, setStateImpl] = React.useState({
674 action: history.action,
675 location: history.location
676 });
677 let {
678 v7_startTransition
679 } = future || {};
680 let setState = React.useCallback(newState => {
681 v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
682 }, [setStateImpl, v7_startTransition]);
683 React.useLayoutEffect(() => history.listen(setState), [history, setState]);
684 return /*#__PURE__*/React.createElement(Router, {
685 basename: basename,
686 children: children,
687 location: state.location,
688 navigationType: state.action,
689 navigator: history,
690 future: future
691 });
692}
693/**
694 * A `<Router>` that accepts a pre-instantiated history object. It's important
695 * to note that using your own history object is highly discouraged and may add
696 * two versions of the history library to your bundles unless you use the same
697 * version of the history library that React Router uses internally.
698 */
699function HistoryRouter({
700 basename,
701 children,
702 future,
703 history
704}) {
705 let [state, setStateImpl] = React.useState({
706 action: history.action,
707 location: history.location
708 });
709 let {
710 v7_startTransition
711 } = future || {};
712 let setState = React.useCallback(newState => {
713 v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
714 }, [setStateImpl, v7_startTransition]);
715 React.useLayoutEffect(() => history.listen(setState), [history, setState]);
716 return /*#__PURE__*/React.createElement(Router, {
717 basename: basename,
718 children: children,
719 location: state.location,
720 navigationType: state.action,
721 navigator: history,
722 future: future
723 });
724}
725{
726 HistoryRouter.displayName = "unstable_HistoryRouter";
727}
728const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
729const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
730
731/**
732 * The public API for rendering a history-aware `<a>`.
733 */
734const Link = /*#__PURE__*/React.forwardRef(function LinkWithRef({
735 onClick,
736 relative,
737 reloadDocument,
738 replace,
739 state,
740 target,
741 to,
742 preventScrollReset,
743 unstable_viewTransition,
744 ...rest
745}, ref) {
746 let {
747 basename
748 } = React.useContext(UNSAFE_NavigationContext);
749
750 // Rendered into <a href> for absolute URLs
751 let absoluteHref;
752 let isExternal = false;
753 if (typeof to === "string" && ABSOLUTE_URL_REGEX.test(to)) {
754 // Render the absolute href server- and client-side
755 absoluteHref = to;
756
757 // Only check for external origins client-side
758 if (isBrowser) {
759 try {
760 let currentUrl = new URL(window.location.href);
761 let targetUrl = to.startsWith("//") ? new URL(currentUrl.protocol + to) : new URL(to);
762 let path = stripBasename(targetUrl.pathname, basename);
763 if (targetUrl.origin === currentUrl.origin && path != null) {
764 // Strip the protocol/origin/basename for same-origin absolute URLs
765 to = path + targetUrl.search + targetUrl.hash;
766 } else {
767 isExternal = true;
768 }
769 } catch (e) {
770 // We can't do external URL detection without a valid URL
771 UNSAFE_warning(false, `<Link to="${to}"> contains an invalid URL which will probably break ` + `when clicked - please update to a valid URL path.`) ;
772 }
773 }
774 }
775
776 // Rendered into <a href> for relative URLs
777 let href = useHref(to, {
778 relative
779 });
780 let internalOnClick = useLinkClickHandler(to, {
781 replace,
782 state,
783 target,
784 preventScrollReset,
785 relative,
786 unstable_viewTransition
787 });
788 function handleClick(event) {
789 if (onClick) onClick(event);
790 if (!event.defaultPrevented) {
791 internalOnClick(event);
792 }
793 }
794 return (
795 /*#__PURE__*/
796 // eslint-disable-next-line jsx-a11y/anchor-has-content
797 React.createElement("a", Object.assign({}, rest, {
798 href: absoluteHref || href,
799 onClick: isExternal || reloadDocument ? onClick : handleClick,
800 ref: ref,
801 target: target
802 }))
803 );
804});
805{
806 Link.displayName = "Link";
807}
808/**
809 * A `<Link>` wrapper that knows if it's "active" or not.
810 */
811const NavLink = /*#__PURE__*/React.forwardRef(function NavLinkWithRef({
812 "aria-current": ariaCurrentProp = "page",
813 caseSensitive = false,
814 className: classNameProp = "",
815 end = false,
816 style: styleProp,
817 to,
818 unstable_viewTransition,
819 children,
820 ...rest
821}, ref) {
822 let path = useResolvedPath(to, {
823 relative: rest.relative
824 });
825 let location = useLocation();
826 let routerState = React.useContext(UNSAFE_DataRouterStateContext);
827 let {
828 navigator,
829 basename
830 } = React.useContext(UNSAFE_NavigationContext);
831 let isTransitioning = routerState != null &&
832 // Conditional usage is OK here because the usage of a data router is static
833 // eslint-disable-next-line react-hooks/rules-of-hooks
834 useViewTransitionState(path) && unstable_viewTransition === true;
835 let toPathname = navigator.encodeLocation ? navigator.encodeLocation(path).pathname : path.pathname;
836 let locationPathname = location.pathname;
837 let nextLocationPathname = routerState && routerState.navigation && routerState.navigation.location ? routerState.navigation.location.pathname : null;
838 if (!caseSensitive) {
839 locationPathname = locationPathname.toLowerCase();
840 nextLocationPathname = nextLocationPathname ? nextLocationPathname.toLowerCase() : null;
841 toPathname = toPathname.toLowerCase();
842 }
843 if (nextLocationPathname && basename) {
844 nextLocationPathname = stripBasename(nextLocationPathname, basename) || nextLocationPathname;
845 }
846
847 // If the `to` has a trailing slash, look at that exact spot. Otherwise,
848 // we're looking for a slash _after_ what's in `to`. For example:
849 //
850 // <NavLink to="/users"> and <NavLink to="/users/">
851 // both want to look for a / at index 6 to match URL `/users/matt`
852 const endSlashPosition = toPathname !== "/" && toPathname.endsWith("/") ? toPathname.length - 1 : toPathname.length;
853 let isActive = locationPathname === toPathname || !end && locationPathname.startsWith(toPathname) && locationPathname.charAt(endSlashPosition) === "/";
854 let isPending = nextLocationPathname != null && (nextLocationPathname === toPathname || !end && nextLocationPathname.startsWith(toPathname) && nextLocationPathname.charAt(toPathname.length) === "/");
855 let renderProps = {
856 isActive,
857 isPending,
858 isTransitioning
859 };
860 let ariaCurrent = isActive ? ariaCurrentProp : undefined;
861 let className;
862 if (typeof classNameProp === "function") {
863 className = classNameProp(renderProps);
864 } else {
865 // If the className prop is not a function, we use a default `active`
866 // class for <NavLink />s that are active. In v5 `active` was the default
867 // value for `activeClassName`, but we are removing that API and can still
868 // use the old default behavior for a cleaner upgrade path and keep the
869 // simple styling rules working as they currently do.
870 className = [classNameProp, isActive ? "active" : null, isPending ? "pending" : null, isTransitioning ? "transitioning" : null].filter(Boolean).join(" ");
871 }
872 let style = typeof styleProp === "function" ? styleProp(renderProps) : styleProp;
873 return /*#__PURE__*/React.createElement(Link, Object.assign({}, rest, {
874 "aria-current": ariaCurrent,
875 className: className,
876 ref: ref,
877 style: style,
878 to: to,
879 unstable_viewTransition: unstable_viewTransition
880 }), typeof children === "function" ? children(renderProps) : children);
881});
882{
883 NavLink.displayName = "NavLink";
884}
885/**
886 * A `@remix-run/router`-aware `<form>`. It behaves like a normal form except
887 * that the interaction with the server is with `fetch` instead of new document
888 * requests, allowing components to add nicer UX to the page as the form is
889 * submitted and returns with data.
890 */
891const Form = /*#__PURE__*/React.forwardRef(({
892 fetcherKey,
893 navigate,
894 reloadDocument,
895 replace,
896 state,
897 method: _method = defaultMethod,
898 action,
899 onSubmit,
900 relative,
901 preventScrollReset,
902 unstable_viewTransition,
903 ...props
904}, forwardedRef) => {
905 let submit = useSubmit();
906 let formAction = useFormAction(action, {
907 relative
908 });
909 let formMethod = _method.toLowerCase() === "get" ? "get" : "post";
910 let submitHandler = event => {
911 onSubmit && onSubmit(event);
912 if (event.defaultPrevented) return;
913 event.preventDefault();
914 let submitter = event.nativeEvent.submitter;
915 let submitMethod = submitter?.getAttribute("formmethod") || _method;
916 submit(submitter || event.currentTarget, {
917 fetcherKey,
918 method: submitMethod,
919 navigate,
920 replace,
921 state,
922 relative,
923 preventScrollReset,
924 unstable_viewTransition
925 });
926 };
927 return /*#__PURE__*/React.createElement("form", Object.assign({
928 ref: forwardedRef,
929 method: formMethod,
930 action: formAction,
931 onSubmit: reloadDocument ? onSubmit : submitHandler
932 }, props));
933});
934{
935 Form.displayName = "Form";
936}
937/**
938 * This component will emulate the browser's scroll restoration on location
939 * changes.
940 */
941function ScrollRestoration({
942 getKey,
943 storageKey
944}) {
945 useScrollRestoration({
946 getKey,
947 storageKey
948 });
949 return null;
950}
951{
952 ScrollRestoration.displayName = "ScrollRestoration";
953}
954//#endregion
955
956////////////////////////////////////////////////////////////////////////////////
957//#region Hooks
958////////////////////////////////////////////////////////////////////////////////
959var DataRouterHook = /*#__PURE__*/function (DataRouterHook) {
960 DataRouterHook["UseScrollRestoration"] = "useScrollRestoration";
961 DataRouterHook["UseSubmit"] = "useSubmit";
962 DataRouterHook["UseSubmitFetcher"] = "useSubmitFetcher";
963 DataRouterHook["UseFetcher"] = "useFetcher";
964 DataRouterHook["useViewTransitionState"] = "useViewTransitionState";
965 return DataRouterHook;
966}(DataRouterHook || {});
967var DataRouterStateHook = /*#__PURE__*/function (DataRouterStateHook) {
968 DataRouterStateHook["UseFetcher"] = "useFetcher";
969 DataRouterStateHook["UseFetchers"] = "useFetchers";
970 DataRouterStateHook["UseScrollRestoration"] = "useScrollRestoration";
971 return DataRouterStateHook;
972}(DataRouterStateHook || {}); // Internal hooks
973function getDataRouterConsoleError(hookName) {
974 return `${hookName} must be used within a data router. See https://reactrouter.com/routers/picking-a-router.`;
975}
976function useDataRouterContext(hookName) {
977 let ctx = React.useContext(UNSAFE_DataRouterContext);
978 !ctx ? UNSAFE_invariant(false, getDataRouterConsoleError(hookName)) : void 0;
979 return ctx;
980}
981function useDataRouterState(hookName) {
982 let state = React.useContext(UNSAFE_DataRouterStateContext);
983 !state ? UNSAFE_invariant(false, getDataRouterConsoleError(hookName)) : void 0;
984 return state;
985}
986
987// External hooks
988
989/**
990 * Handles the click behavior for router `<Link>` components. This is useful if
991 * you need to create custom `<Link>` components with the same click behavior we
992 * use in our exported `<Link>`.
993 */
994function useLinkClickHandler(to, {
995 target,
996 replace: replaceProp,
997 state,
998 preventScrollReset,
999 relative,
1000 unstable_viewTransition
1001} = {}) {
1002 let navigate = useNavigate();
1003 let location = useLocation();
1004 let path = useResolvedPath(to, {
1005 relative
1006 });
1007 return React.useCallback(event => {
1008 if (shouldProcessLinkClick(event, target)) {
1009 event.preventDefault();
1010
1011 // If the URL hasn't changed, a regular <a> will do a replace instead of
1012 // a push, so do the same here unless the replace prop is explicitly set
1013 let replace = replaceProp !== undefined ? replaceProp : createPath(location) === createPath(path);
1014 navigate(to, {
1015 replace,
1016 state,
1017 preventScrollReset,
1018 relative,
1019 unstable_viewTransition
1020 });
1021 }
1022 }, [location, navigate, path, replaceProp, state, target, to, preventScrollReset, relative, unstable_viewTransition]);
1023}
1024
1025/**
1026 * A convenient wrapper for reading and writing search parameters via the
1027 * URLSearchParams interface.
1028 */
1029function useSearchParams(defaultInit) {
1030 UNSAFE_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\n\n` + `If you're unsure how to load polyfills, we recommend you check out ` + `https://polyfill.io/v3/ which provides some recommendations about how ` + `to load polyfills only for users that need them, instead of for every ` + `user.`) ;
1031 let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
1032 let hasSetSearchParamsRef = React.useRef(false);
1033 let location = useLocation();
1034 let searchParams = React.useMemo(() =>
1035 // Only merge in the defaults if we haven't yet called setSearchParams.
1036 // Once we call that we want those to take precedence, otherwise you can't
1037 // remove a param with setSearchParams({}) if it has an initial value
1038 getSearchParamsForLocation(location.search, hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current), [location.search]);
1039 let navigate = useNavigate();
1040 let setSearchParams = React.useCallback((nextInit, navigateOptions) => {
1041 const newSearchParams = createSearchParams(typeof nextInit === "function" ? nextInit(searchParams) : nextInit);
1042 hasSetSearchParamsRef.current = true;
1043 navigate("?" + newSearchParams, navigateOptions);
1044 }, [navigate, searchParams]);
1045 return [searchParams, setSearchParams];
1046}
1047
1048/**
1049 * Submits a HTML `<form>` to the server without reloading the page.
1050 */
1051
1052/**
1053 * Submits a fetcher `<form>` to the server without reloading the page.
1054 */
1055
1056function validateClientSideSubmission() {
1057 if (typeof document === "undefined") {
1058 throw new Error("You are calling submit during the server render. " + "Try calling submit within a `useEffect` or callback instead.");
1059 }
1060}
1061let fetcherId = 0;
1062let getUniqueFetcherId = () => `__${String(++fetcherId)}__`;
1063
1064/**
1065 * Returns a function that may be used to programmatically submit a form (or
1066 * some arbitrary data) to the server.
1067 */
1068function useSubmit() {
1069 let {
1070 router
1071 } = useDataRouterContext(DataRouterHook.UseSubmit);
1072 let {
1073 basename
1074 } = React.useContext(UNSAFE_NavigationContext);
1075 let currentRouteId = UNSAFE_useRouteId();
1076 return React.useCallback((target, options = {}) => {
1077 validateClientSideSubmission();
1078 let {
1079 action,
1080 method,
1081 encType,
1082 formData,
1083 body
1084 } = getFormSubmissionInfo(target, basename);
1085 if (options.navigate === false) {
1086 let key = options.fetcherKey || getUniqueFetcherId();
1087 router.fetch(key, currentRouteId, options.action || action, {
1088 preventScrollReset: options.preventScrollReset,
1089 formData,
1090 body,
1091 formMethod: options.method || method,
1092 formEncType: options.encType || encType,
1093 unstable_flushSync: options.unstable_flushSync
1094 });
1095 } else {
1096 router.navigate(options.action || action, {
1097 preventScrollReset: options.preventScrollReset,
1098 formData,
1099 body,
1100 formMethod: options.method || method,
1101 formEncType: options.encType || encType,
1102 replace: options.replace,
1103 state: options.state,
1104 fromRouteId: currentRouteId,
1105 unstable_flushSync: options.unstable_flushSync,
1106 unstable_viewTransition: options.unstable_viewTransition
1107 });
1108 }
1109 }, [router, basename, currentRouteId]);
1110}
1111
1112// v7: Eventually we should deprecate this entirely in favor of using the
1113// router method directly?
1114function useFormAction(action, {
1115 relative
1116} = {}) {
1117 let {
1118 basename
1119 } = React.useContext(UNSAFE_NavigationContext);
1120 let routeContext = React.useContext(UNSAFE_RouteContext);
1121 !routeContext ? UNSAFE_invariant(false, "useFormAction must be used inside a RouteContext") : void 0;
1122 let [match] = routeContext.matches.slice(-1);
1123 // Shallow clone path so we can modify it below, otherwise we modify the
1124 // object referenced by useMemo inside useResolvedPath
1125 let path = {
1126 ...useResolvedPath(action ? action : ".", {
1127 relative
1128 })
1129 };
1130
1131 // If no action was specified, browsers will persist current search params
1132 // when determining the path, so match that behavior
1133 // https://github.com/remix-run/remix/issues/927
1134 let location = useLocation();
1135 if (action == null) {
1136 // Safe to write to this directly here since if action was undefined, we
1137 // would have called useResolvedPath(".") which will never include a search
1138 path.search = location.search;
1139
1140 // When grabbing search params from the URL, remove any included ?index param
1141 // since it might not apply to our contextual route. We add it back based
1142 // on match.route.index below
1143 let params = new URLSearchParams(path.search);
1144 if (params.has("index") && params.get("index") === "") {
1145 params.delete("index");
1146 path.search = params.toString() ? `?${params.toString()}` : "";
1147 }
1148 }
1149 if ((!action || action === ".") && match.route.index) {
1150 path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
1151 }
1152
1153 // If we're operating within a basename, prepend it to the pathname prior
1154 // to creating the form action. If this is a root navigation, then just use
1155 // the raw basename which allows the basename to have full control over the
1156 // presence of a trailing slash on root actions
1157 if (basename !== "/") {
1158 path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
1159 }
1160 return createPath(path);
1161}
1162// TODO: (v7) Change the useFetcher generic default from `any` to `unknown`
1163/**
1164 * Interacts with route loaders and actions without causing a navigation. Great
1165 * for any interaction that stays on the same page.
1166 */
1167function useFetcher({
1168 key
1169} = {}) {
1170 let {
1171 router
1172 } = useDataRouterContext(DataRouterHook.UseFetcher);
1173 let state = useDataRouterState(DataRouterStateHook.UseFetcher);
1174 let fetcherData = React.useContext(FetchersContext);
1175 let route = React.useContext(UNSAFE_RouteContext);
1176 let routeId = route.matches[route.matches.length - 1]?.route.id;
1177 !fetcherData ? UNSAFE_invariant(false, `useFetcher must be used inside a FetchersContext`) : void 0;
1178 !route ? UNSAFE_invariant(false, `useFetcher must be used inside a RouteContext`) : void 0;
1179 !(routeId != null) ? UNSAFE_invariant(false, `useFetcher can only be used on routes that contain a unique "id"`) : void 0;
1180
1181 // Fetcher key handling
1182 // OK to call conditionally to feature detect `useId`
1183 // eslint-disable-next-line react-hooks/rules-of-hooks
1184 let defaultKey = useIdImpl ? useIdImpl() : "";
1185 let [fetcherKey, setFetcherKey] = React.useState(key || defaultKey);
1186 if (key && key !== fetcherKey) {
1187 setFetcherKey(key);
1188 } else if (!fetcherKey) {
1189 // We will only fall through here when `useId` is not available
1190 setFetcherKey(getUniqueFetcherId());
1191 }
1192
1193 // Registration/cleanup
1194 React.useEffect(() => {
1195 router.getFetcher(fetcherKey);
1196 return () => {
1197 // Tell the router we've unmounted - if v7_fetcherPersist is enabled this
1198 // will not delete immediately but instead queue up a delete after the
1199 // fetcher returns to an `idle` state
1200 router.deleteFetcher(fetcherKey);
1201 };
1202 }, [router, fetcherKey]);
1203
1204 // Fetcher additions
1205 let load = React.useCallback((href, opts) => {
1206 !routeId ? UNSAFE_invariant(false, "No routeId available for fetcher.load()") : void 0;
1207 router.fetch(fetcherKey, routeId, href, opts);
1208 }, [fetcherKey, routeId, router]);
1209 let submitImpl = useSubmit();
1210 let submit = React.useCallback((target, opts) => {
1211 submitImpl(target, {
1212 ...opts,
1213 navigate: false,
1214 fetcherKey
1215 });
1216 }, [fetcherKey, submitImpl]);
1217 let FetcherForm = React.useMemo(() => {
1218 let FetcherForm = /*#__PURE__*/React.forwardRef((props, ref) => {
1219 return /*#__PURE__*/React.createElement(Form, Object.assign({}, props, {
1220 navigate: false,
1221 fetcherKey: fetcherKey,
1222 ref: ref
1223 }));
1224 });
1225 {
1226 FetcherForm.displayName = "fetcher.Form";
1227 }
1228 return FetcherForm;
1229 }, [fetcherKey]);
1230
1231 // Exposed FetcherWithComponents
1232 let fetcher = state.fetchers.get(fetcherKey) || IDLE_FETCHER;
1233 let data = fetcherData.get(fetcherKey);
1234 let fetcherWithComponents = React.useMemo(() => ({
1235 Form: FetcherForm,
1236 submit,
1237 load,
1238 ...fetcher,
1239 data
1240 }), [FetcherForm, submit, load, fetcher, data]);
1241 return fetcherWithComponents;
1242}
1243
1244/**
1245 * Provides all fetchers currently on the page. Useful for layouts and parent
1246 * routes that need to provide pending/optimistic UI regarding the fetch.
1247 */
1248function useFetchers() {
1249 let state = useDataRouterState(DataRouterStateHook.UseFetchers);
1250 return Array.from(state.fetchers.entries()).map(([key, fetcher]) => ({
1251 ...fetcher,
1252 key
1253 }));
1254}
1255const SCROLL_RESTORATION_STORAGE_KEY = "react-router-scroll-positions";
1256let savedScrollPositions = {};
1257
1258/**
1259 * When rendered inside a RouterProvider, will restore scroll positions on navigations
1260 */
1261function useScrollRestoration({
1262 getKey,
1263 storageKey
1264} = {}) {
1265 let {
1266 router
1267 } = useDataRouterContext(DataRouterHook.UseScrollRestoration);
1268 let {
1269 restoreScrollPosition,
1270 preventScrollReset
1271 } = useDataRouterState(DataRouterStateHook.UseScrollRestoration);
1272 let {
1273 basename
1274 } = React.useContext(UNSAFE_NavigationContext);
1275 let location = useLocation();
1276 let matches = useMatches();
1277 let navigation = useNavigation();
1278
1279 // Trigger manual scroll restoration while we're active
1280 React.useEffect(() => {
1281 window.history.scrollRestoration = "manual";
1282 return () => {
1283 window.history.scrollRestoration = "auto";
1284 };
1285 }, []);
1286
1287 // Save positions on pagehide
1288 usePageHide(React.useCallback(() => {
1289 if (navigation.state === "idle") {
1290 let key = (getKey ? getKey(location, matches) : null) || location.key;
1291 savedScrollPositions[key] = window.scrollY;
1292 }
1293 try {
1294 sessionStorage.setItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY, JSON.stringify(savedScrollPositions));
1295 } catch (error) {
1296 UNSAFE_warning(false, `Failed to save scroll positions in sessionStorage, <ScrollRestoration /> will not work properly (${error}).`) ;
1297 }
1298 window.history.scrollRestoration = "auto";
1299 }, [storageKey, getKey, navigation.state, location, matches]));
1300
1301 // Read in any saved scroll locations
1302 if (typeof document !== "undefined") {
1303 // eslint-disable-next-line react-hooks/rules-of-hooks
1304 React.useLayoutEffect(() => {
1305 try {
1306 let sessionPositions = sessionStorage.getItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY);
1307 if (sessionPositions) {
1308 savedScrollPositions = JSON.parse(sessionPositions);
1309 }
1310 } catch (e) {
1311 // no-op, use default empty object
1312 }
1313 }, [storageKey]);
1314
1315 // Enable scroll restoration in the router
1316 // eslint-disable-next-line react-hooks/rules-of-hooks
1317 React.useLayoutEffect(() => {
1318 let getKeyWithoutBasename = getKey && basename !== "/" ? (location, matches) => getKey(
1319 // Strip the basename to match useLocation()
1320 {
1321 ...location,
1322 pathname: stripBasename(location.pathname, basename) || location.pathname
1323 }, matches) : getKey;
1324 let disableScrollRestoration = router?.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKeyWithoutBasename);
1325 return () => disableScrollRestoration && disableScrollRestoration();
1326 }, [router, basename, getKey]);
1327
1328 // Restore scrolling when state.restoreScrollPosition changes
1329 // eslint-disable-next-line react-hooks/rules-of-hooks
1330 React.useLayoutEffect(() => {
1331 // Explicit false means don't do anything (used for submissions)
1332 if (restoreScrollPosition === false) {
1333 return;
1334 }
1335
1336 // been here before, scroll to it
1337 if (typeof restoreScrollPosition === "number") {
1338 window.scrollTo(0, restoreScrollPosition);
1339 return;
1340 }
1341
1342 // try to scroll to the hash
1343 if (location.hash) {
1344 let el = document.getElementById(decodeURIComponent(location.hash.slice(1)));
1345 if (el) {
1346 el.scrollIntoView();
1347 return;
1348 }
1349 }
1350
1351 // Don't reset if this navigation opted out
1352 if (preventScrollReset === true) {
1353 return;
1354 }
1355
1356 // otherwise go to the top on new locations
1357 window.scrollTo(0, 0);
1358 }, [location, restoreScrollPosition, preventScrollReset]);
1359 }
1360}
1361
1362/**
1363 * Setup a callback to be fired on the window's `beforeunload` event. This is
1364 * useful for saving some data to `window.localStorage` just before the page
1365 * refreshes.
1366 *
1367 * Note: The `callback` argument should be a function created with
1368 * `React.useCallback()`.
1369 */
1370function useBeforeUnload(callback, options) {
1371 let {
1372 capture
1373 } = options || {};
1374 React.useEffect(() => {
1375 let opts = capture != null ? {
1376 capture
1377 } : undefined;
1378 window.addEventListener("beforeunload", callback, opts);
1379 return () => {
1380 window.removeEventListener("beforeunload", callback, opts);
1381 };
1382 }, [callback, capture]);
1383}
1384
1385/**
1386 * Setup a callback to be fired on the window's `pagehide` event. This is
1387 * useful for saving some data to `window.localStorage` just before the page
1388 * refreshes. This event is better supported than beforeunload across browsers.
1389 *
1390 * Note: The `callback` argument should be a function created with
1391 * `React.useCallback()`.
1392 */
1393function usePageHide(callback, options) {
1394 let {
1395 capture
1396 } = options || {};
1397 React.useEffect(() => {
1398 let opts = capture != null ? {
1399 capture
1400 } : undefined;
1401 window.addEventListener("pagehide", callback, opts);
1402 return () => {
1403 window.removeEventListener("pagehide", callback, opts);
1404 };
1405 }, [callback, capture]);
1406}
1407
1408/**
1409 * Wrapper around useBlocker to show a window.confirm prompt to users instead
1410 * of building a custom UI with useBlocker.
1411 *
1412 * Warning: This has *a lot of rough edges* and behaves very differently (and
1413 * very incorrectly in some cases) across browsers if user click addition
1414 * back/forward navigations while the confirm is open. Use at your own risk.
1415 */
1416function usePrompt({
1417 when,
1418 message
1419}) {
1420 let blocker = useBlocker(when);
1421 React.useEffect(() => {
1422 if (blocker.state === "blocked") {
1423 let proceed = window.confirm(message);
1424 if (proceed) {
1425 // This timeout is needed to avoid a weird "race" on POP navigations
1426 // between the `window.history` revert navigation and the result of
1427 // `window.confirm`
1428 setTimeout(blocker.proceed, 0);
1429 } else {
1430 blocker.reset();
1431 }
1432 }
1433 }, [blocker, message]);
1434 React.useEffect(() => {
1435 if (blocker.state === "blocked" && !when) {
1436 blocker.reset();
1437 }
1438 }, [blocker, when]);
1439}
1440
1441/**
1442 * Return a boolean indicating if there is an active view transition to the
1443 * given href. You can use this value to render CSS classes or viewTransitionName
1444 * styles onto your elements
1445 *
1446 * @param href The destination href
1447 * @param [opts.relative] Relative routing type ("route" | "path")
1448 */
1449function useViewTransitionState(to, opts = {}) {
1450 let vtContext = React.useContext(ViewTransitionContext);
1451 !(vtContext != null) ? UNSAFE_invariant(false, "`unstable_useViewTransitionState` must be used within `react-router-dom`'s `RouterProvider`. " + "Did you accidentally import `RouterProvider` from `react-router`?") : void 0;
1452 let {
1453 basename
1454 } = useDataRouterContext(DataRouterHook.useViewTransitionState);
1455 let path = useResolvedPath(to, {
1456 relative: opts.relative
1457 });
1458 if (!vtContext.isTransitioning) {
1459 return false;
1460 }
1461 let currentPath = stripBasename(vtContext.currentLocation.pathname, basename) || vtContext.currentLocation.pathname;
1462 let nextPath = stripBasename(vtContext.nextLocation.pathname, basename) || vtContext.nextLocation.pathname;
1463
1464 // Transition is active if we're going to or coming from the indicated
1465 // destination. This ensures that other PUSH navigations that reverse
1466 // an indicated transition apply. I.e., on the list view you have:
1467 //
1468 // <NavLink to="/details/1" unstable_viewTransition>
1469 //
1470 // If you click the breadcrumb back to the list view:
1471 //
1472 // <NavLink to="/list" unstable_viewTransition>
1473 //
1474 // We should apply the transition because it's indicated as active going
1475 // from /list -> /details/1 and therefore should be active on the reverse
1476 // (even though this isn't strictly a POP reverse)
1477 return matchPath(path.pathname, nextPath) != null || matchPath(path.pathname, currentPath) != null;
1478}
1479
1480//#endregion
1481
1482export { BrowserRouter, Form, HashRouter, Link, NavLink, RouterProvider, ScrollRestoration, FetchersContext as UNSAFE_FetchersContext, ViewTransitionContext as UNSAFE_ViewTransitionContext, useScrollRestoration as UNSAFE_useScrollRestoration, createBrowserRouter, createHashRouter, createSearchParams, HistoryRouter as unstable_HistoryRouter, usePrompt as unstable_usePrompt, useViewTransitionState as unstable_useViewTransitionState, useBeforeUnload, useFetcher, useFetchers, useFormAction, useLinkClickHandler, useSearchParams, useSubmit };
1483//# sourceMappingURL=react-router-dom.development.js.map
Note: See TracBrowser for help on using the repository browser.