Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
+329
@@ -0,0 +1,329 @@
|
||||
import type {
|
||||
FormEncType,
|
||||
HTMLFormMethod,
|
||||
RelativeRoutingType,
|
||||
} from "@remix-run/router";
|
||||
import { stripBasename, UNSAFE_warning as warning } from "@remix-run/router";
|
||||
|
||||
export const defaultMethod: HTMLFormMethod = "get";
|
||||
const defaultEncType: FormEncType = "application/x-www-form-urlencoded";
|
||||
|
||||
export function isHtmlElement(object: any): object is HTMLElement {
|
||||
return object != null && typeof object.tagName === "string";
|
||||
}
|
||||
|
||||
export function isButtonElement(object: any): object is HTMLButtonElement {
|
||||
return isHtmlElement(object) && object.tagName.toLowerCase() === "button";
|
||||
}
|
||||
|
||||
export function isFormElement(object: any): object is HTMLFormElement {
|
||||
return isHtmlElement(object) && object.tagName.toLowerCase() === "form";
|
||||
}
|
||||
|
||||
export function isInputElement(object: any): object is HTMLInputElement {
|
||||
return isHtmlElement(object) && object.tagName.toLowerCase() === "input";
|
||||
}
|
||||
|
||||
type LimitedMouseEvent = Pick<
|
||||
MouseEvent,
|
||||
"button" | "metaKey" | "altKey" | "ctrlKey" | "shiftKey"
|
||||
>;
|
||||
|
||||
function isModifiedEvent(event: LimitedMouseEvent) {
|
||||
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
||||
}
|
||||
|
||||
export function shouldProcessLinkClick(
|
||||
event: LimitedMouseEvent,
|
||||
target?: string
|
||||
) {
|
||||
return (
|
||||
event.button === 0 && // Ignore everything but left clicks
|
||||
(!target || target === "_self") && // Let browser handle "target=_blank" etc.
|
||||
!isModifiedEvent(event) // Ignore clicks with modifier keys
|
||||
);
|
||||
}
|
||||
|
||||
export type ParamKeyValuePair = [string, string];
|
||||
|
||||
export type URLSearchParamsInit =
|
||||
| string
|
||||
| ParamKeyValuePair[]
|
||||
| Record<string, string | string[]>
|
||||
| URLSearchParams;
|
||||
|
||||
/**
|
||||
* Creates a URLSearchParams object using the given initializer.
|
||||
*
|
||||
* This is identical to `new URLSearchParams(init)` except it also
|
||||
* supports arrays as values in the object form of the initializer
|
||||
* instead of just strings. This is convenient when you need multiple
|
||||
* values for a given key, but don't want to use an array initializer.
|
||||
*
|
||||
* For example, instead of:
|
||||
*
|
||||
* let searchParams = new URLSearchParams([
|
||||
* ['sort', 'name'],
|
||||
* ['sort', 'price']
|
||||
* ]);
|
||||
*
|
||||
* you can do:
|
||||
*
|
||||
* let searchParams = createSearchParams({
|
||||
* sort: ['name', 'price']
|
||||
* });
|
||||
*/
|
||||
export function createSearchParams(
|
||||
init: URLSearchParamsInit = ""
|
||||
): URLSearchParams {
|
||||
return new URLSearchParams(
|
||||
typeof init === "string" ||
|
||||
Array.isArray(init) ||
|
||||
init instanceof URLSearchParams
|
||||
? init
|
||||
: Object.keys(init).reduce((memo, key) => {
|
||||
let value = init[key];
|
||||
return memo.concat(
|
||||
Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]]
|
||||
);
|
||||
}, [] as ParamKeyValuePair[])
|
||||
);
|
||||
}
|
||||
|
||||
export function getSearchParamsForLocation(
|
||||
locationSearch: string,
|
||||
defaultSearchParams: URLSearchParams | null
|
||||
) {
|
||||
let searchParams = createSearchParams(locationSearch);
|
||||
|
||||
if (defaultSearchParams) {
|
||||
// Use `defaultSearchParams.forEach(...)` here instead of iterating of
|
||||
// `defaultSearchParams.keys()` to work-around a bug in Firefox related to
|
||||
// web extensions. Relevant Bugzilla tickets:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1414602
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1023984
|
||||
defaultSearchParams.forEach((_, key) => {
|
||||
if (!searchParams.has(key)) {
|
||||
defaultSearchParams.getAll(key).forEach((value) => {
|
||||
searchParams.append(key, value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return searchParams;
|
||||
}
|
||||
|
||||
// Thanks https://github.com/sindresorhus/type-fest!
|
||||
type JsonObject = { [Key in string]: JsonValue } & {
|
||||
[Key in string]?: JsonValue | undefined;
|
||||
};
|
||||
type JsonArray = JsonValue[] | readonly JsonValue[];
|
||||
type JsonPrimitive = string | number | boolean | null;
|
||||
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
||||
|
||||
export type SubmitTarget =
|
||||
| HTMLFormElement
|
||||
| HTMLButtonElement
|
||||
| HTMLInputElement
|
||||
| FormData
|
||||
| URLSearchParams
|
||||
| JsonValue
|
||||
| null;
|
||||
|
||||
// One-time check for submitter support
|
||||
let _formDataSupportsSubmitter: boolean | null = null;
|
||||
|
||||
function isFormDataSubmitterSupported() {
|
||||
if (_formDataSupportsSubmitter === null) {
|
||||
try {
|
||||
new FormData(
|
||||
document.createElement("form"),
|
||||
// @ts-expect-error if FormData supports the submitter parameter, this will throw
|
||||
0
|
||||
);
|
||||
_formDataSupportsSubmitter = false;
|
||||
} catch (e) {
|
||||
_formDataSupportsSubmitter = true;
|
||||
}
|
||||
}
|
||||
return _formDataSupportsSubmitter;
|
||||
}
|
||||
|
||||
export interface SubmitOptions {
|
||||
/**
|
||||
* The HTTP method used to submit the form. Overrides `<form method>`.
|
||||
* Defaults to "GET".
|
||||
*/
|
||||
method?: HTMLFormMethod;
|
||||
|
||||
/**
|
||||
* The action URL path used to submit the form. Overrides `<form action>`.
|
||||
* Defaults to the path of the current route.
|
||||
*/
|
||||
action?: string;
|
||||
|
||||
/**
|
||||
* The encoding used to submit the form. Overrides `<form encType>`.
|
||||
* Defaults to "application/x-www-form-urlencoded".
|
||||
*/
|
||||
encType?: FormEncType;
|
||||
|
||||
/**
|
||||
* Indicate a specific fetcherKey to use when using navigate=false
|
||||
*/
|
||||
fetcherKey?: string;
|
||||
|
||||
/**
|
||||
* navigate=false will use a fetcher instead of a navigation
|
||||
*/
|
||||
navigate?: boolean;
|
||||
|
||||
/**
|
||||
* Set `true` to replace the current entry in the browser's history stack
|
||||
* instead of creating a new one (i.e. stay on "the same page"). Defaults
|
||||
* to `false`.
|
||||
*/
|
||||
replace?: boolean;
|
||||
|
||||
/**
|
||||
* State object to add to the history stack entry for this navigation
|
||||
*/
|
||||
state?: any;
|
||||
|
||||
/**
|
||||
* Determines whether the form action is relative to the route hierarchy or
|
||||
* the pathname. Use this if you want to opt out of navigating the route
|
||||
* hierarchy and want to instead route based on /-delimited URL segments
|
||||
*/
|
||||
relative?: RelativeRoutingType;
|
||||
|
||||
/**
|
||||
* In browser-based environments, prevent resetting scroll after this
|
||||
* navigation when using the <ScrollRestoration> component
|
||||
*/
|
||||
preventScrollReset?: boolean;
|
||||
|
||||
/**
|
||||
* Enable flushSync for this navigation's state updates
|
||||
*/
|
||||
unstable_flushSync?: boolean;
|
||||
|
||||
/**
|
||||
* Enable view transitions on this submission navigation
|
||||
*/
|
||||
unstable_viewTransition?: boolean;
|
||||
}
|
||||
|
||||
const supportedFormEncTypes: Set<FormEncType> = new Set([
|
||||
"application/x-www-form-urlencoded",
|
||||
"multipart/form-data",
|
||||
"text/plain",
|
||||
]);
|
||||
|
||||
function getFormEncType(encType: string | null) {
|
||||
if (encType != null && !supportedFormEncTypes.has(encType as FormEncType)) {
|
||||
warning(
|
||||
false,
|
||||
`"${encType}" is not a valid \`encType\` for \`<Form>\`/\`<fetcher.Form>\` ` +
|
||||
`and will default to "${defaultEncType}"`
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
return encType;
|
||||
}
|
||||
|
||||
export function getFormSubmissionInfo(
|
||||
target: SubmitTarget,
|
||||
basename: string
|
||||
): {
|
||||
action: string | null;
|
||||
method: string;
|
||||
encType: string;
|
||||
formData: FormData | undefined;
|
||||
body: any;
|
||||
} {
|
||||
let method: string;
|
||||
let action: string | null;
|
||||
let encType: string;
|
||||
let formData: FormData | undefined;
|
||||
let body: any;
|
||||
|
||||
if (isFormElement(target)) {
|
||||
// When grabbing the action from the element, it will have had the basename
|
||||
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
||||
// re-prefix in the router
|
||||
let attr = target.getAttribute("action");
|
||||
action = attr ? stripBasename(attr, basename) : null;
|
||||
method = target.getAttribute("method") || defaultMethod;
|
||||
encType = getFormEncType(target.getAttribute("enctype")) || defaultEncType;
|
||||
|
||||
formData = new FormData(target);
|
||||
} else if (
|
||||
isButtonElement(target) ||
|
||||
(isInputElement(target) &&
|
||||
(target.type === "submit" || target.type === "image"))
|
||||
) {
|
||||
let form = target.form;
|
||||
|
||||
if (form == null) {
|
||||
throw new Error(
|
||||
`Cannot submit a <button> or <input type="submit"> without a <form>`
|
||||
);
|
||||
}
|
||||
|
||||
// <button>/<input type="submit"> may override attributes of <form>
|
||||
|
||||
// When grabbing the action from the element, it will have had the basename
|
||||
// prefixed to ensure non-JS scenarios work, so strip it since we'll
|
||||
// re-prefix in the router
|
||||
let attr = target.getAttribute("formaction") || form.getAttribute("action");
|
||||
action = attr ? stripBasename(attr, basename) : null;
|
||||
|
||||
method =
|
||||
target.getAttribute("formmethod") ||
|
||||
form.getAttribute("method") ||
|
||||
defaultMethod;
|
||||
encType =
|
||||
getFormEncType(target.getAttribute("formenctype")) ||
|
||||
getFormEncType(form.getAttribute("enctype")) ||
|
||||
defaultEncType;
|
||||
|
||||
// Build a FormData object populated from a form and submitter
|
||||
formData = new FormData(form, target);
|
||||
|
||||
// If this browser doesn't support the `FormData(el, submitter)` format,
|
||||
// then tack on the submitter value at the end. This is a lightweight
|
||||
// solution that is not 100% spec compliant. For complete support in older
|
||||
// browsers, consider using the `formdata-submitter-polyfill` package
|
||||
if (!isFormDataSubmitterSupported()) {
|
||||
let { name, type, value } = target;
|
||||
if (type === "image") {
|
||||
let prefix = name ? `${name}.` : "";
|
||||
formData.append(`${prefix}x`, "0");
|
||||
formData.append(`${prefix}y`, "0");
|
||||
} else if (name) {
|
||||
formData.append(name, value);
|
||||
}
|
||||
}
|
||||
} else if (isHtmlElement(target)) {
|
||||
throw new Error(
|
||||
`Cannot submit element that is not <form>, <button>, or ` +
|
||||
`<input type="submit|image">`
|
||||
);
|
||||
} else {
|
||||
method = defaultMethod;
|
||||
action = null;
|
||||
encType = defaultEncType;
|
||||
body = target;
|
||||
}
|
||||
|
||||
// Send body for <Form encType="text/plain" so we encode it into text
|
||||
if (formData && encType === "text/plain") {
|
||||
body = formData;
|
||||
formData = undefined;
|
||||
}
|
||||
|
||||
return { action, method: method.toLowerCase(), encType, formData, body };
|
||||
}
|
||||
+2029
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+738
@@ -0,0 +1,738 @@
|
||||
import type {
|
||||
InitialEntry,
|
||||
LazyRouteFunction,
|
||||
Location,
|
||||
MemoryHistory,
|
||||
RelativeRoutingType,
|
||||
Router as RemixRouter,
|
||||
RouterState,
|
||||
RouterSubscriber,
|
||||
To,
|
||||
TrackedPromise,
|
||||
} from "@remix-run/router";
|
||||
import {
|
||||
AbortedDeferredError,
|
||||
Action as NavigationType,
|
||||
createMemoryHistory,
|
||||
UNSAFE_getResolveToMatches as getResolveToMatches,
|
||||
UNSAFE_invariant as invariant,
|
||||
parsePath,
|
||||
resolveTo,
|
||||
stripBasename,
|
||||
UNSAFE_warning as warning,
|
||||
} from "@remix-run/router";
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
DataRouteObject,
|
||||
IndexRouteObject,
|
||||
Navigator,
|
||||
NonIndexRouteObject,
|
||||
RouteMatch,
|
||||
RouteObject,
|
||||
} from "./context";
|
||||
import {
|
||||
AwaitContext,
|
||||
DataRouterContext,
|
||||
DataRouterStateContext,
|
||||
LocationContext,
|
||||
NavigationContext,
|
||||
RouteContext,
|
||||
} from "./context";
|
||||
import {
|
||||
_renderMatches,
|
||||
useAsyncValue,
|
||||
useInRouterContext,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useOutlet,
|
||||
useRoutes,
|
||||
useRoutesImpl,
|
||||
} from "./hooks";
|
||||
|
||||
export interface FutureConfig {
|
||||
v7_relativeSplatPath: boolean;
|
||||
v7_startTransition: boolean;
|
||||
}
|
||||
|
||||
export interface RouterProviderProps {
|
||||
fallbackElement?: React.ReactNode;
|
||||
router: RemixRouter;
|
||||
// Only accept future flags relevant to rendering behavior
|
||||
// routing flags should be accessed via router.future
|
||||
future?: Partial<Pick<FutureConfig, "v7_startTransition">>;
|
||||
}
|
||||
|
||||
/**
|
||||
Webpack + React 17 fails to compile on any of the following because webpack
|
||||
complains that `startTransition` doesn't exist in `React`:
|
||||
* import { startTransition } from "react"
|
||||
* import * as React from from "react";
|
||||
"startTransition" in React ? React.startTransition(() => setState()) : setState()
|
||||
* import * as React from from "react";
|
||||
"startTransition" in React ? React["startTransition"](() => setState()) : setState()
|
||||
|
||||
Moving it to a constant such as the following solves the Webpack/React 17 issue:
|
||||
* import * as React from from "react";
|
||||
const START_TRANSITION = "startTransition";
|
||||
START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
|
||||
|
||||
However, that introduces webpack/terser minification issues in production builds
|
||||
in React 18 where minification/obfuscation ends up removing the call of
|
||||
React.startTransition entirely from the first half of the ternary. Grabbing
|
||||
this exported reference once up front resolves that issue.
|
||||
|
||||
See https://github.com/remix-run/react-router/issues/10579
|
||||
*/
|
||||
const START_TRANSITION = "startTransition";
|
||||
const startTransitionImpl = React[START_TRANSITION];
|
||||
|
||||
/**
|
||||
* Given a Remix Router instance, render the appropriate UI
|
||||
*/
|
||||
export function RouterProvider({
|
||||
fallbackElement,
|
||||
router,
|
||||
future,
|
||||
}: RouterProviderProps): React.ReactElement {
|
||||
let [state, setStateImpl] = React.useState(router.state);
|
||||
let { v7_startTransition } = future || {};
|
||||
|
||||
let setState = React.useCallback<RouterSubscriber>(
|
||||
(newState: RouterState) => {
|
||||
if (v7_startTransition && startTransitionImpl) {
|
||||
startTransitionImpl(() => setStateImpl(newState));
|
||||
} else {
|
||||
setStateImpl(newState);
|
||||
}
|
||||
},
|
||||
[setStateImpl, v7_startTransition]
|
||||
);
|
||||
|
||||
// Need to use a layout effect here so we are subscribed early enough to
|
||||
// pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
|
||||
React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
|
||||
|
||||
React.useEffect(() => {
|
||||
warning(
|
||||
fallbackElement == null || !router.future.v7_partialHydration,
|
||||
"`<RouterProvider fallbackElement>` is deprecated when using " +
|
||||
"`v7_partialHydration`, use a `HydrateFallback` component instead"
|
||||
);
|
||||
// Only log this once on initial mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
let navigator = React.useMemo((): Navigator => {
|
||||
return {
|
||||
createHref: router.createHref,
|
||||
encodeLocation: router.encodeLocation,
|
||||
go: (n) => router.navigate(n),
|
||||
push: (to, state, opts) =>
|
||||
router.navigate(to, {
|
||||
state,
|
||||
preventScrollReset: opts?.preventScrollReset,
|
||||
}),
|
||||
replace: (to, state, opts) =>
|
||||
router.navigate(to, {
|
||||
replace: true,
|
||||
state,
|
||||
preventScrollReset: opts?.preventScrollReset,
|
||||
}),
|
||||
};
|
||||
}, [router]);
|
||||
|
||||
let basename = router.basename || "/";
|
||||
|
||||
let dataRouterContext = React.useMemo(
|
||||
() => ({
|
||||
router,
|
||||
navigator,
|
||||
static: false,
|
||||
basename,
|
||||
}),
|
||||
[router, navigator, basename]
|
||||
);
|
||||
|
||||
// The fragment and {null} here are important! We need them to keep React 18's
|
||||
// useId happy when we are server-rendering since we may have a <script> here
|
||||
// containing the hydrated server-side staticContext (from StaticRouterProvider).
|
||||
// useId relies on the component tree structure to generate deterministic id's
|
||||
// so we need to ensure it remains the same on the client even though
|
||||
// we don't need the <script> tag
|
||||
return (
|
||||
<>
|
||||
<DataRouterContext.Provider value={dataRouterContext}>
|
||||
<DataRouterStateContext.Provider value={state}>
|
||||
<Router
|
||||
basename={basename}
|
||||
location={state.location}
|
||||
navigationType={state.historyAction}
|
||||
navigator={navigator}
|
||||
future={{
|
||||
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
|
||||
}}
|
||||
>
|
||||
{state.initialized || router.future.v7_partialHydration ? (
|
||||
<DataRoutes
|
||||
routes={router.routes}
|
||||
future={router.future}
|
||||
state={state}
|
||||
/>
|
||||
) : (
|
||||
fallbackElement
|
||||
)}
|
||||
</Router>
|
||||
</DataRouterStateContext.Provider>
|
||||
</DataRouterContext.Provider>
|
||||
{null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DataRoutes({
|
||||
routes,
|
||||
future,
|
||||
state,
|
||||
}: {
|
||||
routes: DataRouteObject[];
|
||||
future: RemixRouter["future"];
|
||||
state: RouterState;
|
||||
}): React.ReactElement | null {
|
||||
return useRoutesImpl(routes, undefined, state, future);
|
||||
}
|
||||
|
||||
export interface MemoryRouterProps {
|
||||
basename?: string;
|
||||
children?: React.ReactNode;
|
||||
initialEntries?: InitialEntry[];
|
||||
initialIndex?: number;
|
||||
future?: Partial<FutureConfig>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `<Router>` that stores all entries in memory.
|
||||
*
|
||||
* @see https://reactrouter.com/router-components/memory-router
|
||||
*/
|
||||
export function MemoryRouter({
|
||||
basename,
|
||||
children,
|
||||
initialEntries,
|
||||
initialIndex,
|
||||
future,
|
||||
}: MemoryRouterProps): React.ReactElement {
|
||||
let historyRef = React.useRef<MemoryHistory>();
|
||||
if (historyRef.current == null) {
|
||||
historyRef.current = createMemoryHistory({
|
||||
initialEntries,
|
||||
initialIndex,
|
||||
v5Compat: true,
|
||||
});
|
||||
}
|
||||
|
||||
let history = historyRef.current;
|
||||
let [state, setStateImpl] = React.useState({
|
||||
action: history.action,
|
||||
location: history.location,
|
||||
});
|
||||
let { v7_startTransition } = future || {};
|
||||
let setState = React.useCallback(
|
||||
(newState: { action: NavigationType; location: Location }) => {
|
||||
v7_startTransition && startTransitionImpl
|
||||
? startTransitionImpl(() => setStateImpl(newState))
|
||||
: setStateImpl(newState);
|
||||
},
|
||||
[setStateImpl, v7_startTransition]
|
||||
);
|
||||
|
||||
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
|
||||
|
||||
return (
|
||||
<Router
|
||||
basename={basename}
|
||||
children={children}
|
||||
location={state.location}
|
||||
navigationType={state.action}
|
||||
navigator={history}
|
||||
future={future}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface NavigateProps {
|
||||
to: To;
|
||||
replace?: boolean;
|
||||
state?: any;
|
||||
relative?: RelativeRoutingType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current location.
|
||||
*
|
||||
* Note: This API is mostly useful in React.Component subclasses that are not
|
||||
* able to use hooks. In functional components, we recommend you use the
|
||||
* `useNavigate` hook instead.
|
||||
*
|
||||
* @see https://reactrouter.com/components/navigate
|
||||
*/
|
||||
export function Navigate({
|
||||
to,
|
||||
replace,
|
||||
state,
|
||||
relative,
|
||||
}: NavigateProps): null {
|
||||
invariant(
|
||||
useInRouterContext(),
|
||||
// TODO: This error is probably because they somehow have 2 versions of
|
||||
// the router loaded. We can help them understand how to avoid that.
|
||||
`<Navigate> may be used only in the context of a <Router> component.`
|
||||
);
|
||||
|
||||
let { future, static: isStatic } = React.useContext(NavigationContext);
|
||||
|
||||
warning(
|
||||
!isStatic,
|
||||
`<Navigate> must not be used on the initial render in a <StaticRouter>. ` +
|
||||
`This is a no-op, but you should modify your code so the <Navigate> is ` +
|
||||
`only ever rendered in response to some user interaction or state change.`
|
||||
);
|
||||
|
||||
let { matches } = React.useContext(RouteContext);
|
||||
let { pathname: locationPathname } = useLocation();
|
||||
let navigate = useNavigate();
|
||||
|
||||
// Resolve the path outside of the effect so that when effects run twice in
|
||||
// StrictMode they navigate to the same place
|
||||
let path = resolveTo(
|
||||
to,
|
||||
getResolveToMatches(matches, future.v7_relativeSplatPath),
|
||||
locationPathname,
|
||||
relative === "path"
|
||||
);
|
||||
let jsonPath = JSON.stringify(path);
|
||||
|
||||
React.useEffect(
|
||||
() => navigate(JSON.parse(jsonPath), { replace, state, relative }),
|
||||
[navigate, jsonPath, relative, replace, state]
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface OutletProps {
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the child route's element, if there is one.
|
||||
*
|
||||
* @see https://reactrouter.com/components/outlet
|
||||
*/
|
||||
export function Outlet(props: OutletProps): React.ReactElement | null {
|
||||
return useOutlet(props.context);
|
||||
}
|
||||
|
||||
export interface PathRouteProps {
|
||||
caseSensitive?: NonIndexRouteObject["caseSensitive"];
|
||||
path?: NonIndexRouteObject["path"];
|
||||
id?: NonIndexRouteObject["id"];
|
||||
lazy?: LazyRouteFunction<NonIndexRouteObject>;
|
||||
loader?: NonIndexRouteObject["loader"];
|
||||
action?: NonIndexRouteObject["action"];
|
||||
hasErrorBoundary?: NonIndexRouteObject["hasErrorBoundary"];
|
||||
shouldRevalidate?: NonIndexRouteObject["shouldRevalidate"];
|
||||
handle?: NonIndexRouteObject["handle"];
|
||||
index?: false;
|
||||
children?: React.ReactNode;
|
||||
element?: React.ReactNode | null;
|
||||
hydrateFallbackElement?: React.ReactNode | null;
|
||||
errorElement?: React.ReactNode | null;
|
||||
Component?: React.ComponentType | null;
|
||||
HydrateFallback?: React.ComponentType | null;
|
||||
ErrorBoundary?: React.ComponentType | null;
|
||||
}
|
||||
|
||||
export interface LayoutRouteProps extends PathRouteProps {}
|
||||
|
||||
export interface IndexRouteProps {
|
||||
caseSensitive?: IndexRouteObject["caseSensitive"];
|
||||
path?: IndexRouteObject["path"];
|
||||
id?: IndexRouteObject["id"];
|
||||
lazy?: LazyRouteFunction<IndexRouteObject>;
|
||||
loader?: IndexRouteObject["loader"];
|
||||
action?: IndexRouteObject["action"];
|
||||
hasErrorBoundary?: IndexRouteObject["hasErrorBoundary"];
|
||||
shouldRevalidate?: IndexRouteObject["shouldRevalidate"];
|
||||
handle?: IndexRouteObject["handle"];
|
||||
index: true;
|
||||
children?: undefined;
|
||||
element?: React.ReactNode | null;
|
||||
hydrateFallbackElement?: React.ReactNode | null;
|
||||
errorElement?: React.ReactNode | null;
|
||||
Component?: React.ComponentType | null;
|
||||
HydrateFallback?: React.ComponentType | null;
|
||||
ErrorBoundary?: React.ComponentType | null;
|
||||
}
|
||||
|
||||
export type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps;
|
||||
|
||||
/**
|
||||
* Declares an element that should be rendered at a certain URL path.
|
||||
*
|
||||
* @see https://reactrouter.com/components/route
|
||||
*/
|
||||
export function Route(_props: RouteProps): React.ReactElement | null {
|
||||
invariant(
|
||||
false,
|
||||
`A <Route> is only ever to be used as the child of <Routes> element, ` +
|
||||
`never rendered directly. Please wrap your <Route> in a <Routes>.`
|
||||
);
|
||||
}
|
||||
|
||||
export interface RouterProps {
|
||||
basename?: string;
|
||||
children?: React.ReactNode;
|
||||
location: Partial<Location> | string;
|
||||
navigationType?: NavigationType;
|
||||
navigator: Navigator;
|
||||
static?: boolean;
|
||||
future?: Partial<Pick<FutureConfig, "v7_relativeSplatPath">>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides location context for the rest of the app.
|
||||
*
|
||||
* Note: You usually won't render a `<Router>` directly. Instead, you'll render a
|
||||
* router that is more specific to your environment such as a `<BrowserRouter>`
|
||||
* in web browsers or a `<StaticRouter>` for server rendering.
|
||||
*
|
||||
* @see https://reactrouter.com/router-components/router
|
||||
*/
|
||||
export function Router({
|
||||
basename: basenameProp = "/",
|
||||
children = null,
|
||||
location: locationProp,
|
||||
navigationType = NavigationType.Pop,
|
||||
navigator,
|
||||
static: staticProp = false,
|
||||
future,
|
||||
}: RouterProps): React.ReactElement | null {
|
||||
invariant(
|
||||
!useInRouterContext(),
|
||||
`You cannot render a <Router> inside another <Router>.` +
|
||||
` You should never have more than one in your app.`
|
||||
);
|
||||
|
||||
// Preserve trailing slashes on basename, so we can let the user control
|
||||
// the enforcement of trailing slashes throughout the app
|
||||
let basename = basenameProp.replace(/^\/*/, "/");
|
||||
let navigationContext = React.useMemo(
|
||||
() => ({
|
||||
basename,
|
||||
navigator,
|
||||
static: staticProp,
|
||||
future: {
|
||||
v7_relativeSplatPath: false,
|
||||
...future,
|
||||
},
|
||||
}),
|
||||
[basename, future, navigator, staticProp]
|
||||
);
|
||||
|
||||
if (typeof locationProp === "string") {
|
||||
locationProp = parsePath(locationProp);
|
||||
}
|
||||
|
||||
let {
|
||||
pathname = "/",
|
||||
search = "",
|
||||
hash = "",
|
||||
state = null,
|
||||
key = "default",
|
||||
} = locationProp;
|
||||
|
||||
let locationContext = React.useMemo(() => {
|
||||
let trailingPathname = stripBasename(pathname, basename);
|
||||
|
||||
if (trailingPathname == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
location: {
|
||||
pathname: trailingPathname,
|
||||
search,
|
||||
hash,
|
||||
state,
|
||||
key,
|
||||
},
|
||||
navigationType,
|
||||
};
|
||||
}, [basename, pathname, search, hash, state, key, navigationType]);
|
||||
|
||||
warning(
|
||||
locationContext != null,
|
||||
`<Router basename="${basename}"> is not able to match the URL ` +
|
||||
`"${pathname}${search}${hash}" because it does not start with the ` +
|
||||
`basename, so the <Router> won't render anything.`
|
||||
);
|
||||
|
||||
if (locationContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={navigationContext}>
|
||||
<LocationContext.Provider children={children} value={locationContext} />
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export interface RoutesProps {
|
||||
children?: React.ReactNode;
|
||||
location?: Partial<Location> | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A container for a nested tree of `<Route>` elements that renders the branch
|
||||
* that best matches the current location.
|
||||
*
|
||||
* @see https://reactrouter.com/components/routes
|
||||
*/
|
||||
export function Routes({
|
||||
children,
|
||||
location,
|
||||
}: RoutesProps): React.ReactElement | null {
|
||||
return useRoutes(createRoutesFromChildren(children), location);
|
||||
}
|
||||
|
||||
export interface AwaitResolveRenderFunction {
|
||||
(data: Awaited<any>): React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AwaitProps {
|
||||
children: React.ReactNode | AwaitResolveRenderFunction;
|
||||
errorElement?: React.ReactNode;
|
||||
resolve: TrackedPromise | any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to use for rendering lazily loaded data from returning defer()
|
||||
* in a loader function
|
||||
*/
|
||||
export function Await({ children, errorElement, resolve }: AwaitProps) {
|
||||
return (
|
||||
<AwaitErrorBoundary resolve={resolve} errorElement={errorElement}>
|
||||
<ResolveAwait>{children}</ResolveAwait>
|
||||
</AwaitErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
type AwaitErrorBoundaryProps = React.PropsWithChildren<{
|
||||
errorElement?: React.ReactNode;
|
||||
resolve: TrackedPromise | any;
|
||||
}>;
|
||||
|
||||
type AwaitErrorBoundaryState = {
|
||||
error: any;
|
||||
};
|
||||
|
||||
enum AwaitRenderStatus {
|
||||
pending,
|
||||
success,
|
||||
error,
|
||||
}
|
||||
|
||||
const neverSettledPromise = new Promise(() => {});
|
||||
|
||||
class AwaitErrorBoundary extends React.Component<
|
||||
AwaitErrorBoundaryProps,
|
||||
AwaitErrorBoundaryState
|
||||
> {
|
||||
constructor(props: AwaitErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, errorInfo: any) {
|
||||
console.error(
|
||||
"<Await> caught the following error during render",
|
||||
error,
|
||||
errorInfo
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { children, errorElement, resolve } = this.props;
|
||||
|
||||
let promise: TrackedPromise | null = null;
|
||||
let status: AwaitRenderStatus = AwaitRenderStatus.pending;
|
||||
|
||||
if (!(resolve instanceof Promise)) {
|
||||
// Didn't get a promise - provide as a resolved promise
|
||||
status = AwaitRenderStatus.success;
|
||||
promise = Promise.resolve();
|
||||
Object.defineProperty(promise, "_tracked", { get: () => true });
|
||||
Object.defineProperty(promise, "_data", { get: () => resolve });
|
||||
} else if (this.state.error) {
|
||||
// Caught a render error, provide it as a rejected promise
|
||||
status = AwaitRenderStatus.error;
|
||||
let renderError = this.state.error;
|
||||
promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
|
||||
Object.defineProperty(promise, "_tracked", { get: () => true });
|
||||
Object.defineProperty(promise, "_error", { get: () => renderError });
|
||||
} else if ((resolve as TrackedPromise)._tracked) {
|
||||
// Already tracked promise - check contents
|
||||
promise = resolve;
|
||||
status =
|
||||
"_error" in promise
|
||||
? AwaitRenderStatus.error
|
||||
: "_data" in promise
|
||||
? AwaitRenderStatus.success
|
||||
: AwaitRenderStatus.pending;
|
||||
} else {
|
||||
// Raw (untracked) promise - track it
|
||||
status = AwaitRenderStatus.pending;
|
||||
Object.defineProperty(resolve, "_tracked", { get: () => true });
|
||||
promise = resolve.then(
|
||||
(data: any) =>
|
||||
Object.defineProperty(resolve, "_data", { get: () => data }),
|
||||
(error: any) =>
|
||||
Object.defineProperty(resolve, "_error", { get: () => error })
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
status === AwaitRenderStatus.error &&
|
||||
promise._error instanceof AbortedDeferredError
|
||||
) {
|
||||
// Freeze the UI by throwing a never resolved promise
|
||||
throw neverSettledPromise;
|
||||
}
|
||||
|
||||
if (status === AwaitRenderStatus.error && !errorElement) {
|
||||
// No errorElement, throw to the nearest route-level error boundary
|
||||
throw promise._error;
|
||||
}
|
||||
|
||||
if (status === AwaitRenderStatus.error) {
|
||||
// Render via our errorElement
|
||||
return <AwaitContext.Provider value={promise} children={errorElement} />;
|
||||
}
|
||||
|
||||
if (status === AwaitRenderStatus.success) {
|
||||
// Render children with resolved value
|
||||
return <AwaitContext.Provider value={promise} children={children} />;
|
||||
}
|
||||
|
||||
// Throw to the suspense boundary
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Indirection to leverage useAsyncValue for a render-prop API on `<Await>`
|
||||
*/
|
||||
function ResolveAwait({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode | AwaitResolveRenderFunction;
|
||||
}) {
|
||||
let data = useAsyncValue();
|
||||
let toRender = typeof children === "function" ? children(data) : children;
|
||||
return <>{toRender}</>;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// UTILS
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Creates a route config from a React "children" object, which is usually
|
||||
* either a `<Route>` element or an array of them. Used internally by
|
||||
* `<Routes>` to create a route config from its children.
|
||||
*
|
||||
* @see https://reactrouter.com/utils/create-routes-from-children
|
||||
*/
|
||||
export function createRoutesFromChildren(
|
||||
children: React.ReactNode,
|
||||
parentPath: number[] = []
|
||||
): RouteObject[] {
|
||||
let routes: RouteObject[] = [];
|
||||
|
||||
React.Children.forEach(children, (element, index) => {
|
||||
if (!React.isValidElement(element)) {
|
||||
// Ignore non-elements. This allows people to more easily inline
|
||||
// conditionals in their route config.
|
||||
return;
|
||||
}
|
||||
|
||||
let treePath = [...parentPath, index];
|
||||
|
||||
if (element.type === React.Fragment) {
|
||||
// Transparently support React.Fragment and its children.
|
||||
routes.push.apply(
|
||||
routes,
|
||||
createRoutesFromChildren(element.props.children, treePath)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
invariant(
|
||||
element.type === Route,
|
||||
`[${
|
||||
typeof element.type === "string" ? element.type : element.type.name
|
||||
}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`
|
||||
);
|
||||
|
||||
invariant(
|
||||
!element.props.index || !element.props.children,
|
||||
"An index route cannot have child routes."
|
||||
);
|
||||
|
||||
let route: RouteObject = {
|
||||
id: element.props.id || treePath.join("-"),
|
||||
caseSensitive: element.props.caseSensitive,
|
||||
element: element.props.element,
|
||||
Component: element.props.Component,
|
||||
index: element.props.index,
|
||||
path: element.props.path,
|
||||
loader: element.props.loader,
|
||||
action: element.props.action,
|
||||
errorElement: element.props.errorElement,
|
||||
ErrorBoundary: element.props.ErrorBoundary,
|
||||
hasErrorBoundary:
|
||||
element.props.ErrorBoundary != null ||
|
||||
element.props.errorElement != null,
|
||||
shouldRevalidate: element.props.shouldRevalidate,
|
||||
handle: element.props.handle,
|
||||
lazy: element.props.lazy,
|
||||
};
|
||||
|
||||
if (element.props.children) {
|
||||
route.children = createRoutesFromChildren(
|
||||
element.props.children,
|
||||
treePath
|
||||
);
|
||||
}
|
||||
|
||||
routes.push(route);
|
||||
});
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the result of `matchRoutes()` into a React element.
|
||||
*/
|
||||
export function renderMatches(
|
||||
matches: RouteMatch[] | null
|
||||
): React.ReactElement | null {
|
||||
return _renderMatches(matches);
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
import * as React from "react";
|
||||
import type {
|
||||
AgnosticIndexRouteObject,
|
||||
AgnosticNonIndexRouteObject,
|
||||
AgnosticRouteMatch,
|
||||
History,
|
||||
LazyRouteFunction,
|
||||
Location,
|
||||
Action as NavigationType,
|
||||
RelativeRoutingType,
|
||||
Router,
|
||||
StaticHandlerContext,
|
||||
To,
|
||||
TrackedPromise,
|
||||
} from "@remix-run/router";
|
||||
|
||||
// Create react-specific types from the agnostic types in @remix-run/router to
|
||||
// export from react-router
|
||||
export interface IndexRouteObject {
|
||||
caseSensitive?: AgnosticIndexRouteObject["caseSensitive"];
|
||||
path?: AgnosticIndexRouteObject["path"];
|
||||
id?: AgnosticIndexRouteObject["id"];
|
||||
loader?: AgnosticIndexRouteObject["loader"];
|
||||
action?: AgnosticIndexRouteObject["action"];
|
||||
hasErrorBoundary?: AgnosticIndexRouteObject["hasErrorBoundary"];
|
||||
shouldRevalidate?: AgnosticIndexRouteObject["shouldRevalidate"];
|
||||
handle?: AgnosticIndexRouteObject["handle"];
|
||||
index: true;
|
||||
children?: undefined;
|
||||
element?: React.ReactNode | null;
|
||||
hydrateFallbackElement?: React.ReactNode | null;
|
||||
errorElement?: React.ReactNode | null;
|
||||
Component?: React.ComponentType | null;
|
||||
HydrateFallback?: React.ComponentType | null;
|
||||
ErrorBoundary?: React.ComponentType | null;
|
||||
lazy?: LazyRouteFunction<RouteObject>;
|
||||
}
|
||||
|
||||
export interface NonIndexRouteObject {
|
||||
caseSensitive?: AgnosticNonIndexRouteObject["caseSensitive"];
|
||||
path?: AgnosticNonIndexRouteObject["path"];
|
||||
id?: AgnosticNonIndexRouteObject["id"];
|
||||
loader?: AgnosticNonIndexRouteObject["loader"];
|
||||
action?: AgnosticNonIndexRouteObject["action"];
|
||||
hasErrorBoundary?: AgnosticNonIndexRouteObject["hasErrorBoundary"];
|
||||
shouldRevalidate?: AgnosticNonIndexRouteObject["shouldRevalidate"];
|
||||
handle?: AgnosticNonIndexRouteObject["handle"];
|
||||
index?: false;
|
||||
children?: RouteObject[];
|
||||
element?: React.ReactNode | null;
|
||||
hydrateFallbackElement?: React.ReactNode | null;
|
||||
errorElement?: React.ReactNode | null;
|
||||
Component?: React.ComponentType | null;
|
||||
HydrateFallback?: React.ComponentType | null;
|
||||
ErrorBoundary?: React.ComponentType | null;
|
||||
lazy?: LazyRouteFunction<RouteObject>;
|
||||
}
|
||||
|
||||
export type RouteObject = IndexRouteObject | NonIndexRouteObject;
|
||||
|
||||
export type DataRouteObject = RouteObject & {
|
||||
children?: DataRouteObject[];
|
||||
id: string;
|
||||
};
|
||||
|
||||
export interface RouteMatch<
|
||||
ParamKey extends string = string,
|
||||
RouteObjectType extends RouteObject = RouteObject
|
||||
> extends AgnosticRouteMatch<ParamKey, RouteObjectType> {}
|
||||
|
||||
export interface DataRouteMatch extends RouteMatch<string, DataRouteObject> {}
|
||||
|
||||
export interface DataRouterContextObject
|
||||
// Omit `future` since those can be pulled from the `router`
|
||||
// `NavigationContext` needs future since it doesn't have a `router` in all cases
|
||||
extends Omit<NavigationContextObject, "future"> {
|
||||
router: Router;
|
||||
staticContext?: StaticHandlerContext;
|
||||
}
|
||||
|
||||
export const DataRouterContext =
|
||||
React.createContext<DataRouterContextObject | null>(null);
|
||||
if (__DEV__) {
|
||||
DataRouterContext.displayName = "DataRouter";
|
||||
}
|
||||
|
||||
export const DataRouterStateContext = React.createContext<
|
||||
Router["state"] | null
|
||||
>(null);
|
||||
if (__DEV__) {
|
||||
DataRouterStateContext.displayName = "DataRouterState";
|
||||
}
|
||||
|
||||
export const AwaitContext = React.createContext<TrackedPromise | null>(null);
|
||||
if (__DEV__) {
|
||||
AwaitContext.displayName = "Await";
|
||||
}
|
||||
|
||||
export interface NavigateOptions {
|
||||
replace?: boolean;
|
||||
state?: any;
|
||||
preventScrollReset?: boolean;
|
||||
relative?: RelativeRoutingType;
|
||||
unstable_flushSync?: boolean;
|
||||
unstable_viewTransition?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Navigator is a "location changer"; it's how you get to different locations.
|
||||
*
|
||||
* Every history instance conforms to the Navigator interface, but the
|
||||
* distinction is useful primarily when it comes to the low-level `<Router>` API
|
||||
* where both the location and a navigator must be provided separately in order
|
||||
* to avoid "tearing" that may occur in a suspense-enabled app if the action
|
||||
* and/or location were to be read directly from the history instance.
|
||||
*/
|
||||
export interface Navigator {
|
||||
createHref: History["createHref"];
|
||||
// Optional for backwards-compat with Router/HistoryRouter usage (edge case)
|
||||
encodeLocation?: History["encodeLocation"];
|
||||
go: History["go"];
|
||||
push(to: To, state?: any, opts?: NavigateOptions): void;
|
||||
replace(to: To, state?: any, opts?: NavigateOptions): void;
|
||||
}
|
||||
|
||||
interface NavigationContextObject {
|
||||
basename: string;
|
||||
navigator: Navigator;
|
||||
static: boolean;
|
||||
future: {
|
||||
v7_relativeSplatPath: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const NavigationContext = React.createContext<NavigationContextObject>(
|
||||
null!
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
NavigationContext.displayName = "Navigation";
|
||||
}
|
||||
|
||||
interface LocationContextObject {
|
||||
location: Location;
|
||||
navigationType: NavigationType;
|
||||
}
|
||||
|
||||
export const LocationContext = React.createContext<LocationContextObject>(
|
||||
null!
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
LocationContext.displayName = "Location";
|
||||
}
|
||||
|
||||
export interface RouteContextObject {
|
||||
outlet: React.ReactElement | null;
|
||||
matches: RouteMatch[];
|
||||
isDataRoute: boolean;
|
||||
}
|
||||
|
||||
export const RouteContext = React.createContext<RouteContextObject>({
|
||||
outlet: null,
|
||||
matches: [],
|
||||
isDataRoute: false,
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
RouteContext.displayName = "Route";
|
||||
}
|
||||
|
||||
export const RouteErrorContext = React.createContext<any>(null);
|
||||
|
||||
if (__DEV__) {
|
||||
RouteErrorContext.displayName = "RouteError";
|
||||
}
|
||||
+1104
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user