source: imaps-frontend/node_modules/react-router-dom/dist/react-router-dom.development.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

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