securityos/node_modules/next/dist/esm/client/link.js

406 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use client";
import React from "react";
import { resolveHref } from "./resolve-href";
import { isLocalURL } from "../shared/lib/router/utils/is-local-url";
import { formatUrl } from "../shared/lib/router/utils/format-url";
import { isAbsoluteUrl } from "../shared/lib/utils";
import { addLocale } from "./add-locale";
import { RouterContext } from "../shared/lib/router-context.shared-runtime";
import { AppRouterContext } from "../shared/lib/app-router-context.shared-runtime";
import { useIntersection } from "./use-intersection";
import { getDomainLocale } from "./get-domain-locale";
import { addBasePath } from "./add-base-path";
import { PrefetchKind } from "./components/router-reducer/router-reducer-types";
const prefetched = new Set();
function prefetch(router, href, as, options, appOptions, isAppRouter) {
if (typeof window === "undefined") {
return;
}
// app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router.
if (!isAppRouter && !isLocalURL(href)) {
return;
}
// We should only dedupe requests when experimental.optimisticClientCache is
// disabled.
if (!options.bypassPrefetchedCheck) {
const locale = // Let the link's locale prop override the default router locale.
typeof options.locale !== "undefined" ? options.locale : "locale" in router ? router.locale : undefined;
const prefetchedKey = href + "%" + as + "%" + locale;
// If we've already fetched the key, then don't prefetch it again!
if (prefetched.has(prefetchedKey)) {
return;
}
// Mark this URL as prefetched.
prefetched.add(prefetchedKey);
}
const prefetchPromise = isAppRouter ? router.prefetch(href, appOptions) : router.prefetch(href, as, options);
// Prefetch the JSON page if asked (only in the client)
// We need to handle a prefetch error here since we may be
// loading with priority which can reject but we don't
// want to force navigation since this is only a prefetch
Promise.resolve(prefetchPromise).catch((err)=>{
if (process.env.NODE_ENV !== "production") {
// rethrow to show invalid URL errors
throw err;
}
});
}
function isModifiedEvent(event) {
const eventTarget = event.currentTarget;
const target = eventTarget.getAttribute("target");
return target && target !== "_self" || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || // triggers resource download
event.nativeEvent && event.nativeEvent.which === 2;
}
function linkClicked(e, router, href, as, replace, shallow, scroll, locale, isAppRouter, prefetchEnabled) {
const { nodeName } = e.currentTarget;
// anchors inside an svg have a lowercase nodeName
const isAnchorNodeName = nodeName.toUpperCase() === "A";
if (isAnchorNodeName && (isModifiedEvent(e) || // app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router.
!isAppRouter && !isLocalURL(href))) {
// ignore click for browsers default behavior
return;
}
e.preventDefault();
const navigate = ()=>{
// If the router is an NextRouter instance it will have `beforePopState`
const routerScroll = scroll != null ? scroll : true;
if ("beforePopState" in router) {
router[replace ? "replace" : "push"](href, as, {
shallow,
locale,
scroll: routerScroll
});
} else {
router[replace ? "replace" : "push"](as || href, {
forceOptimisticNavigation: !prefetchEnabled,
scroll: routerScroll
});
}
};
if (isAppRouter) {
React.startTransition(navigate);
} else {
navigate();
}
}
function formatStringOrUrl(urlObjOrString) {
if (typeof urlObjOrString === "string") {
return urlObjOrString;
}
return formatUrl(urlObjOrString);
}
/**
* React Component that enables client-side transitions between routes.
*/ const Link = /*#__PURE__*/ React.forwardRef(function LinkComponent(props, forwardedRef) {
let children;
const { href: hrefProp, as: asProp, children: childrenProp, prefetch: prefetchProp = null, passHref, replace, shallow, scroll, locale, onClick, onMouseEnter: onMouseEnterProp, onTouchStart: onTouchStartProp, legacyBehavior = false, ...restProps } = props;
children = childrenProp;
if (legacyBehavior && (typeof children === "string" || typeof children === "number")) {
children = /*#__PURE__*/ React.createElement("a", null, children);
}
const pagesRouter = React.useContext(RouterContext);
const appRouter = React.useContext(AppRouterContext);
const router = pagesRouter != null ? pagesRouter : appRouter;
// We're in the app directory if there is no pages router.
const isAppRouter = !pagesRouter;
const prefetchEnabled = prefetchProp !== false;
/**
* The possible states for prefetch are:
* - null: this is the default "auto" mode, where we will prefetch partially if the link is in the viewport
* - true: we will prefetch if the link is visible and prefetch the full page, not just partially
* - false: we will not prefetch if in the viewport at all
*/ const appPrefetchKind = prefetchProp === null ? PrefetchKind.AUTO : PrefetchKind.FULL;
if (process.env.NODE_ENV !== "production") {
function createPropError(args) {
return new Error("Failed prop type: The prop `" + args.key + "` expects a " + args.expected + " in `<Link>`, but got `" + args.actual + "` instead." + (typeof window !== "undefined" ? "\nOpen your browser's console to view the Component stack trace." : ""));
}
// TypeScript trick for type-guarding:
const requiredPropsGuard = {
href: true
};
const requiredProps = Object.keys(requiredPropsGuard);
requiredProps.forEach((key)=>{
if (key === "href") {
if (props[key] == null || typeof props[key] !== "string" && typeof props[key] !== "object") {
throw createPropError({
key,
expected: "`string` or `object`",
actual: props[key] === null ? "null" : typeof props[key]
});
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _ = key;
}
});
// TypeScript trick for type-guarding:
const optionalPropsGuard = {
as: true,
replace: true,
scroll: true,
shallow: true,
passHref: true,
prefetch: true,
locale: true,
onClick: true,
onMouseEnter: true,
onTouchStart: true,
legacyBehavior: true
};
const optionalProps = Object.keys(optionalPropsGuard);
optionalProps.forEach((key)=>{
const valType = typeof props[key];
if (key === "as") {
if (props[key] && valType !== "string" && valType !== "object") {
throw createPropError({
key,
expected: "`string` or `object`",
actual: valType
});
}
} else if (key === "locale") {
if (props[key] && valType !== "string") {
throw createPropError({
key,
expected: "`string`",
actual: valType
});
}
} else if (key === "onClick" || key === "onMouseEnter" || key === "onTouchStart") {
if (props[key] && valType !== "function") {
throw createPropError({
key,
expected: "`function`",
actual: valType
});
}
} else if (key === "replace" || key === "scroll" || key === "shallow" || key === "passHref" || key === "prefetch" || key === "legacyBehavior") {
if (props[key] != null && valType !== "boolean") {
throw createPropError({
key,
expected: "`boolean`",
actual: valType
});
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _ = key;
}
});
// This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasWarned = React.useRef(false);
if (props.prefetch && !hasWarned.current && !isAppRouter) {
hasWarned.current = true;
console.warn("Next.js auto-prefetches automatically based on viewport. The prefetch attribute is no longer needed. More: https://nextjs.org/docs/messages/prefetch-true-deprecated");
}
}
if (process.env.NODE_ENV !== "production") {
if (isAppRouter && !asProp) {
let href;
if (typeof hrefProp === "string") {
href = hrefProp;
} else if (typeof hrefProp === "object" && typeof hrefProp.pathname === "string") {
href = hrefProp.pathname;
}
if (href) {
const hasDynamicSegment = href.split("/").some((segment)=>segment.startsWith("[") && segment.endsWith("]"));
if (hasDynamicSegment) {
throw new Error("Dynamic href `" + href + "` found in <Link> while using the `/app` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href");
}
}
}
}
const { href, as } = React.useMemo(()=>{
if (!pagesRouter) {
const resolvedHref = formatStringOrUrl(hrefProp);
return {
href: resolvedHref,
as: asProp ? formatStringOrUrl(asProp) : resolvedHref
};
}
const [resolvedHref, resolvedAs] = resolveHref(pagesRouter, hrefProp, true);
return {
href: resolvedHref,
as: asProp ? resolveHref(pagesRouter, asProp) : resolvedAs || resolvedHref
};
}, [
pagesRouter,
hrefProp,
asProp
]);
const previousHref = React.useRef(href);
const previousAs = React.useRef(as);
// This will return the first child, if multiple are provided it will throw an error
let child;
if (legacyBehavior) {
if (process.env.NODE_ENV === "development") {
if (onClick) {
console.warn('"onClick" was passed to <Link> with `href` of `' + hrefProp + '` but "legacyBehavior" was set. The legacy behavior requires onClick be set on the child of next/link');
}
if (onMouseEnterProp) {
console.warn('"onMouseEnter" was passed to <Link> with `href` of `' + hrefProp + '` but "legacyBehavior" was set. The legacy behavior requires onMouseEnter be set on the child of next/link');
}
try {
child = React.Children.only(children);
} catch (err) {
if (!children) {
throw new Error("No children were passed to <Link> with `href` of `" + hrefProp + "` but one child is required https://nextjs.org/docs/messages/link-no-children");
}
throw new Error("Multiple children were passed to <Link> with `href` of `" + hrefProp + "` but only one child is supported https://nextjs.org/docs/messages/link-multiple-children" + (typeof window !== "undefined" ? " \nOpen your browser's console to view the Component stack trace." : ""));
}
} else {
child = React.Children.only(children);
}
} else {
if (process.env.NODE_ENV === "development") {
if ((children == null ? void 0 : children.type) === "a") {
throw new Error("Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>.\nLearn more: https://nextjs.org/docs/messages/invalid-new-link-with-extra-anchor");
}
}
}
const childRef = legacyBehavior ? child && typeof child === "object" && child.ref : forwardedRef;
const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
rootMargin: "200px"
});
const setRef = React.useCallback((el)=>{
// Before the link getting observed, check if visible state need to be reset
if (previousAs.current !== as || previousHref.current !== href) {
resetVisible();
previousAs.current = as;
previousHref.current = href;
}
setIntersectionRef(el);
if (childRef) {
if (typeof childRef === "function") childRef(el);
else if (typeof childRef === "object") {
childRef.current = el;
}
}
}, [
as,
childRef,
href,
resetVisible,
setIntersectionRef
]);
// Prefetch the URL if we haven't already and it's visible.
React.useEffect(()=>{
// in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page.
if (process.env.NODE_ENV !== "production") {
return;
}
if (!router) {
return;
}
// If we don't need to prefetch the URL, don't do prefetch.
if (!isVisible || !prefetchEnabled) {
return;
}
// Prefetch the URL.
prefetch(router, href, as, {
locale
}, {
kind: appPrefetchKind
}, isAppRouter);
}, [
as,
href,
isVisible,
locale,
prefetchEnabled,
pagesRouter == null ? void 0 : pagesRouter.locale,
router,
isAppRouter,
appPrefetchKind
]);
const childProps = {
ref: setRef,
onClick (e) {
if (process.env.NODE_ENV !== "production") {
if (!e) {
throw new Error('Component rendered inside next/link has to pass click event to "onClick" prop.');
}
}
if (!legacyBehavior && typeof onClick === "function") {
onClick(e);
}
if (legacyBehavior && child.props && typeof child.props.onClick === "function") {
child.props.onClick(e);
}
if (!router) {
return;
}
if (e.defaultPrevented) {
return;
}
linkClicked(e, router, href, as, replace, shallow, scroll, locale, isAppRouter, prefetchEnabled);
},
onMouseEnter (e) {
if (!legacyBehavior && typeof onMouseEnterProp === "function") {
onMouseEnterProp(e);
}
if (legacyBehavior && child.props && typeof child.props.onMouseEnter === "function") {
child.props.onMouseEnter(e);
}
if (!router) {
return;
}
if ((!prefetchEnabled || process.env.NODE_ENV === "development") && isAppRouter) {
return;
}
prefetch(router, href, as, {
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true
}, {
kind: appPrefetchKind
}, isAppRouter);
},
onTouchStart (e) {
if (!legacyBehavior && typeof onTouchStartProp === "function") {
onTouchStartProp(e);
}
if (legacyBehavior && child.props && typeof child.props.onTouchStart === "function") {
child.props.onTouchStart(e);
}
if (!router) {
return;
}
if (!prefetchEnabled && isAppRouter) {
return;
}
prefetch(router, href, as, {
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true
}, {
kind: appPrefetchKind
}, isAppRouter);
}
};
// If child is an <a> tag and doesn't have a href attribute, or if the 'passHref' property is
// defined, we specify the current 'href', so that repetition is not needed by the user.
// If the url is absolute, we can bypass the logic to prepend the domain and locale.
if (isAbsoluteUrl(as)) {
childProps.href = as;
} else if (!legacyBehavior || passHref || child.type === "a" && !("href" in child.props)) {
const curLocale = typeof locale !== "undefined" ? locale : pagesRouter == null ? void 0 : pagesRouter.locale;
// we only render domain locales if we are currently on a domain locale
// so that locale links are still visitable in development/preview envs
const localeDomain = (pagesRouter == null ? void 0 : pagesRouter.isLocaleDomain) && getDomainLocale(as, curLocale, pagesRouter == null ? void 0 : pagesRouter.locales, pagesRouter == null ? void 0 : pagesRouter.domainLocales);
childProps.href = localeDomain || addBasePath(addLocale(as, curLocale, pagesRouter == null ? void 0 : pagesRouter.defaultLocale));
}
return legacyBehavior ? /*#__PURE__*/ React.cloneElement(child, childProps) : /*#__PURE__*/ React.createElement("a", {
...restProps,
...childProps
}, children);
});
export default Link;
//# sourceMappingURL=link.js.map