link.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. "client";
  2. "use strict";
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports.default = void 0;
  7. var _interop_require_default = require("@swc/helpers/lib/_interop_require_default.js").default;
  8. var _object_without_properties_loose = require("@swc/helpers/lib/_object_without_properties_loose.js").default;
  9. var _react = _interop_require_default(require("react"));
  10. var _router = require("../shared/lib/router/router");
  11. var _addLocale = require("./add-locale");
  12. var _routerContext = require("../shared/lib/router-context");
  13. var _appRouterContext = require("../shared/lib/app-router-context");
  14. var _useIntersection = require("./use-intersection");
  15. var _getDomainLocale = require("./get-domain-locale");
  16. var _addBasePath = require("./add-base-path");
  17. 'client';
  18. const prefetched = {};
  19. function prefetch(router, href, as, options) {
  20. if (typeof window === 'undefined' || !router) return;
  21. if (!(0, _router).isLocalURL(href)) return;
  22. // Prefetch the JSON page if asked (only in the client)
  23. // We need to handle a prefetch error here since we may be
  24. // loading with priority which can reject but we don't
  25. // want to force navigation since this is only a prefetch
  26. Promise.resolve(router.prefetch(href, as, options)).catch((err)=>{
  27. if (process.env.NODE_ENV !== 'production') {
  28. // rethrow to show invalid URL errors
  29. throw err;
  30. }
  31. });
  32. const curLocale = options && typeof options.locale !== 'undefined' ? options.locale : router && router.locale;
  33. // Join on an invalid URI character
  34. prefetched[href + '%' + as + (curLocale ? '%' + curLocale : '')] = true;
  35. }
  36. function isModifiedEvent(event) {
  37. const { target } = event.currentTarget;
  38. return target && target !== '_self' || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.nativeEvent && event.nativeEvent.which === 2;
  39. }
  40. function linkClicked(e, router, href, as, replace, shallow, scroll, locale, isAppRouter, prefetchEnabled) {
  41. const { nodeName } = e.currentTarget;
  42. // anchors inside an svg have a lowercase nodeName
  43. const isAnchorNodeName = nodeName.toUpperCase() === 'A';
  44. if (isAnchorNodeName && (isModifiedEvent(e) || !(0, _router).isLocalURL(href))) {
  45. // ignore click for browser’s default behavior
  46. return;
  47. }
  48. e.preventDefault();
  49. const navigate = ()=>{
  50. // If the router is an NextRouter instance it will have `beforePopState`
  51. if ('beforePopState' in router) {
  52. router[replace ? 'replace' : 'push'](href, as, {
  53. shallow,
  54. locale,
  55. scroll
  56. });
  57. } else {
  58. // If `beforePopState` doesn't exist on the router it's the AppRouter.
  59. const method = replace ? 'replace' : 'push';
  60. router[method](href, {
  61. forceOptimisticNavigation: !prefetchEnabled
  62. });
  63. }
  64. };
  65. if (isAppRouter) {
  66. // @ts-expect-error startTransition exists.
  67. _react.default.startTransition(navigate);
  68. } else {
  69. navigate();
  70. }
  71. }
  72. const Link = /*#__PURE__*/ _react.default.forwardRef(function LinkComponent(props, forwardedRef) {
  73. if (process.env.NODE_ENV !== 'production') {
  74. function createPropError(args) {
  75. 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." : ''));
  76. }
  77. // TypeScript trick for type-guarding:
  78. const requiredPropsGuard = {
  79. href: true
  80. };
  81. const requiredProps = Object.keys(requiredPropsGuard);
  82. requiredProps.forEach((key)=>{
  83. if (key === 'href') {
  84. if (props[key] == null || typeof props[key] !== 'string' && typeof props[key] !== 'object') {
  85. throw createPropError({
  86. key,
  87. expected: '`string` or `object`',
  88. actual: props[key] === null ? 'null' : typeof props[key]
  89. });
  90. }
  91. } else {
  92. // TypeScript trick for type-guarding:
  93. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  94. const _ = key;
  95. }
  96. });
  97. // TypeScript trick for type-guarding:
  98. const optionalPropsGuard = {
  99. as: true,
  100. replace: true,
  101. scroll: true,
  102. shallow: true,
  103. passHref: true,
  104. prefetch: true,
  105. locale: true,
  106. onClick: true,
  107. onMouseEnter: true,
  108. onTouchStart: true,
  109. legacyBehavior: true
  110. };
  111. const optionalProps = Object.keys(optionalPropsGuard);
  112. optionalProps.forEach((key)=>{
  113. const valType = typeof props[key];
  114. if (key === 'as') {
  115. if (props[key] && valType !== 'string' && valType !== 'object') {
  116. throw createPropError({
  117. key,
  118. expected: '`string` or `object`',
  119. actual: valType
  120. });
  121. }
  122. } else if (key === 'locale') {
  123. if (props[key] && valType !== 'string') {
  124. throw createPropError({
  125. key,
  126. expected: '`string`',
  127. actual: valType
  128. });
  129. }
  130. } else if (key === 'onClick' || key === 'onMouseEnter' || key === 'onTouchStart') {
  131. if (props[key] && valType !== 'function') {
  132. throw createPropError({
  133. key,
  134. expected: '`function`',
  135. actual: valType
  136. });
  137. }
  138. } else if (key === 'replace' || key === 'scroll' || key === 'shallow' || key === 'passHref' || key === 'prefetch' || key === 'legacyBehavior') {
  139. if (props[key] != null && valType !== 'boolean') {
  140. throw createPropError({
  141. key,
  142. expected: '`boolean`',
  143. actual: valType
  144. });
  145. }
  146. } else {
  147. // TypeScript trick for type-guarding:
  148. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  149. const _ = key;
  150. }
  151. });
  152. // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes
  153. // eslint-disable-next-line react-hooks/rules-of-hooks
  154. const hasWarned = _react.default.useRef(false);
  155. if (props.prefetch && !hasWarned.current) {
  156. hasWarned.current = true;
  157. 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');
  158. }
  159. }
  160. let children;
  161. const { href: hrefProp , as: asProp , children: childrenProp , prefetch: prefetchProp , passHref , replace , shallow , scroll , locale , onClick , onMouseEnter , onTouchStart , legacyBehavior =Boolean(process.env.__NEXT_NEW_LINK_BEHAVIOR) !== true } = props, restProps = _object_without_properties_loose(props, [
  162. "href",
  163. "as",
  164. "children",
  165. "prefetch",
  166. "passHref",
  167. "replace",
  168. "shallow",
  169. "scroll",
  170. "locale",
  171. "onClick",
  172. "onMouseEnter",
  173. "onTouchStart",
  174. "legacyBehavior"
  175. ]);
  176. children = childrenProp;
  177. if (legacyBehavior && (typeof children === 'string' || typeof children === 'number')) {
  178. children = /*#__PURE__*/ _react.default.createElement("a", null, children);
  179. }
  180. const p = prefetchProp !== false;
  181. let router = _react.default.useContext(_routerContext.RouterContext);
  182. // TODO-APP: type error. Remove `as any`
  183. const appRouter = _react.default.useContext(_appRouterContext.AppRouterContext);
  184. if (appRouter) {
  185. router = appRouter;
  186. }
  187. const { href , as } = _react.default.useMemo(()=>{
  188. const [resolvedHref, resolvedAs] = (0, _router).resolveHref(router, hrefProp, true);
  189. return {
  190. href: resolvedHref,
  191. as: asProp ? (0, _router).resolveHref(router, asProp) : resolvedAs || resolvedHref
  192. };
  193. }, [
  194. router,
  195. hrefProp,
  196. asProp
  197. ]);
  198. const previousHref = _react.default.useRef(href);
  199. const previousAs = _react.default.useRef(as);
  200. // This will return the first child, if multiple are provided it will throw an error
  201. let child;
  202. if (legacyBehavior) {
  203. if (process.env.NODE_ENV === 'development') {
  204. if (onClick) {
  205. 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`);
  206. }
  207. if (onMouseEnter) {
  208. 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`);
  209. }
  210. try {
  211. child = _react.default.Children.only(children);
  212. } catch (err) {
  213. if (!children) {
  214. 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`);
  215. }
  216. 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." : ''));
  217. }
  218. } else {
  219. child = _react.default.Children.only(children);
  220. }
  221. }
  222. const childRef = legacyBehavior ? child && typeof child === 'object' && child.ref : forwardedRef;
  223. const [setIntersectionRef, isVisible, resetVisible] = (0, _useIntersection).useIntersection({
  224. rootMargin: '200px'
  225. });
  226. const setRef = _react.default.useCallback((el)=>{
  227. // Before the link getting observed, check if visible state need to be reset
  228. if (previousAs.current !== as || previousHref.current !== href) {
  229. resetVisible();
  230. previousAs.current = as;
  231. previousHref.current = href;
  232. }
  233. setIntersectionRef(el);
  234. if (childRef) {
  235. if (typeof childRef === 'function') childRef(el);
  236. else if (typeof childRef === 'object') {
  237. childRef.current = el;
  238. }
  239. }
  240. }, [
  241. as,
  242. childRef,
  243. href,
  244. resetVisible,
  245. setIntersectionRef
  246. ]);
  247. _react.default.useEffect(()=>{
  248. const shouldPrefetch = isVisible && p && (0, _router).isLocalURL(href);
  249. const curLocale = typeof locale !== 'undefined' ? locale : router && router.locale;
  250. const isPrefetched = prefetched[href + '%' + as + (curLocale ? '%' + curLocale : '')];
  251. if (shouldPrefetch && !isPrefetched) {
  252. prefetch(router, href, as, {
  253. locale: curLocale
  254. });
  255. }
  256. }, [
  257. as,
  258. href,
  259. isVisible,
  260. locale,
  261. p,
  262. router
  263. ]);
  264. const childProps = {
  265. ref: setRef,
  266. onClick: (e)=>{
  267. if (process.env.NODE_ENV !== 'production') {
  268. if (!e) {
  269. throw new Error(`Component rendered inside next/link has to pass click event to "onClick" prop.`);
  270. }
  271. }
  272. if (!legacyBehavior && typeof onClick === 'function') {
  273. onClick(e);
  274. }
  275. if (legacyBehavior && child.props && typeof child.props.onClick === 'function') {
  276. child.props.onClick(e);
  277. }
  278. if (!e.defaultPrevented) {
  279. linkClicked(e, router, href, as, replace, shallow, scroll, locale, Boolean(appRouter), p);
  280. }
  281. },
  282. onMouseEnter: (e)=>{
  283. if (!legacyBehavior && typeof onMouseEnter === 'function') {
  284. onMouseEnter(e);
  285. }
  286. if (legacyBehavior && child.props && typeof child.props.onMouseEnter === 'function') {
  287. child.props.onMouseEnter(e);
  288. }
  289. // Check for not prefetch disabled in page using appRouter
  290. if (!(!p && appRouter)) {
  291. if ((0, _router).isLocalURL(href)) {
  292. prefetch(router, href, as, {
  293. priority: true
  294. });
  295. }
  296. }
  297. },
  298. onTouchStart: (e)=>{
  299. if (!legacyBehavior && typeof onTouchStart === 'function') {
  300. onTouchStart(e);
  301. }
  302. if (legacyBehavior && child.props && typeof child.props.onTouchStart === 'function') {
  303. child.props.onTouchStart(e);
  304. }
  305. // Check for not prefetch disabled in page using appRouter
  306. if (!(!p && appRouter)) {
  307. if ((0, _router).isLocalURL(href)) {
  308. prefetch(router, href, as, {
  309. priority: true
  310. });
  311. }
  312. }
  313. }
  314. };
  315. // If child is an <a> tag and doesn't have a href attribute, or if the 'passHref' property is
  316. // defined, we specify the current 'href', so that repetition is not needed by the user
  317. if (!legacyBehavior || passHref || child.type === 'a' && !('href' in child.props)) {
  318. const curLocale = typeof locale !== 'undefined' ? locale : router && router.locale;
  319. // we only render domain locales if we are currently on a domain locale
  320. // so that locale links are still visitable in development/preview envs
  321. const localeDomain = router && router.isLocaleDomain && (0, _getDomainLocale).getDomainLocale(as, curLocale, router.locales, router.domainLocales);
  322. childProps.href = localeDomain || (0, _addBasePath).addBasePath((0, _addLocale).addLocale(as, curLocale, router && router.defaultLocale));
  323. }
  324. return legacyBehavior ? /*#__PURE__*/ _react.default.cloneElement(child, childProps) : /*#__PURE__*/ _react.default.createElement("a", Object.assign({}, restProps, childProps), children);
  325. });
  326. var _default = Link;
  327. exports.default = _default;
  328. if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
  329. Object.defineProperty(exports.default, '__esModule', { value: true });
  330. Object.assign(exports.default, exports);
  331. module.exports = exports.default;
  332. }
  333. //# sourceMappingURL=link.js.map