"use client"; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = OuterLayoutRouter; exports.InnerLayoutRouter = InnerLayoutRouter; var _extends = require("@swc/helpers/lib/_extends.js").default; var _interop_require_default = require("@swc/helpers/lib/_interop_require_default.js").default; var _interop_require_wildcard = require("@swc/helpers/lib/_interop_require_wildcard.js").default; var _react = _interop_require_wildcard(require("react")); var _reactDom = _interop_require_default(require("react-dom")); var _appRouterContext = require("../../shared/lib/app-router-context"); var _appRouter = require("./app-router"); var _infinitePromise = require("./infinite-promise"); var _errorBoundary = require("./error-boundary"); var _matchSegments = require("./match-segments"); var _navigation = require("./navigation"); function OuterLayoutRouter({ parallelRouterKey , segmentPath , childProp , error , errorStyles , templateStyles , loading , loadingStyles , hasLoading , template , notFound , notFoundStyles , rootLayoutIncluded }) { const context = (0, _react).useContext(_appRouterContext.LayoutRouterContext); if (!context) { throw new Error('invariant expected layout router to be mounted'); } const { childNodes , tree , url } = context; // Get the current parallelRouter cache node let childNodesForParallelRouter = childNodes.get(parallelRouterKey); // If the parallel router cache node does not exist yet, create it. // This writes to the cache when there is no item in the cache yet. It never *overwrites* existing cache items which is why it's safe in concurrent mode. if (!childNodesForParallelRouter) { childNodes.set(parallelRouterKey, new Map()); childNodesForParallelRouter = childNodes.get(parallelRouterKey); } // Get the active segment in the tree // The reason arrays are used in the data format is that these are transferred from the server to the browser so it's optimized to save bytes. const treeSegment = tree[1][parallelRouterKey][0]; const childPropSegment = Array.isArray(childProp.segment) ? childProp.segment[1] : childProp.segment; // If segment is an array it's a dynamic route and we want to read the dynamic route value as the segment to get from the cache. const currentChildSegment = Array.isArray(treeSegment) ? treeSegment[1] : treeSegment; /** * Decides which segments to keep rendering, all segments that are not active will be wrapped in ``. */ // TODO-APP: Add handling of `` when it's available. const preservedSegments = [ currentChildSegment ]; return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, preservedSegments.map((preservedSegment)=>{ return(/* - Error boundary - Only renders error boundary if error component is provided. - Rendered for each segment to ensure they have their own error state. - Loading boundary - Only renders suspense boundary if loading components is provided. - Rendered for each segment to ensure they have their own loading state. - Passed to the router during rendering to ensure it can be immediately rendered when suspending on a Flight fetch. */ /*#__PURE__*/ _react.default.createElement(_appRouterContext.TemplateContext.Provider, { key: preservedSegment, value: /*#__PURE__*/ _react.default.createElement(_errorBoundary.ErrorBoundary, { errorComponent: error, errorStyles: errorStyles }, /*#__PURE__*/ _react.default.createElement(LoadingBoundary, { hasLoading: hasLoading, loading: loading, loadingStyles: loadingStyles }, /*#__PURE__*/ _react.default.createElement(NotFoundBoundary, { notFound: notFound, notFoundStyles: notFoundStyles }, /*#__PURE__*/ _react.default.createElement(RedirectBoundary, null, /*#__PURE__*/ _react.default.createElement(InnerLayoutRouter, { parallelRouterKey: parallelRouterKey, url: url, tree: tree, childNodes: childNodesForParallelRouter, childProp: childPropSegment === preservedSegment ? childProp : null, segmentPath: segmentPath, path: preservedSegment, isActive: currentChildSegment === preservedSegment, rootLayoutIncluded: rootLayoutIncluded }))))) }, /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, templateStyles, template))); })); } /** * Add refetch marker to router state at the point of the current layout segment. * This ensures the response returned is not further down than the current layout segment. */ function walkAddRefetch(segmentPathToWalk, treeToRecreate) { if (segmentPathToWalk) { const [segment, parallelRouteKey] = segmentPathToWalk; const isLast = segmentPathToWalk.length === 2; if ((0, _matchSegments).matchSegment(treeToRecreate[0], segment)) { if (treeToRecreate[1].hasOwnProperty(parallelRouteKey)) { if (isLast) { const subTree = walkAddRefetch(undefined, treeToRecreate[1][parallelRouteKey]); return [ treeToRecreate[0], _extends({}, treeToRecreate[1], { [parallelRouteKey]: [ subTree[0], subTree[1], subTree[2], 'refetch', ] }), ]; } return [ treeToRecreate[0], _extends({}, treeToRecreate[1], { [parallelRouteKey]: walkAddRefetch(segmentPathToWalk.slice(2), treeToRecreate[1][parallelRouteKey]) }), ]; } } } return treeToRecreate; } // TODO-APP: Replace with new React API for finding dom nodes without a `ref` when available /** * Wraps ReactDOM.findDOMNode with additional logic to hide React Strict Mode warning */ function findDOMNode(instance) { // Tree-shake for server bundle if (typeof window === undefined) return null; // Only apply strict mode warning when not in production if (process.env.NODE_ENV !== 'production') { const originalConsoleError = console.error; try { console.error = (...messages)=>{ // Ignore strict mode warning for the findDomNode call below if (!messages[0].includes('Warning: %s is deprecated in StrictMode.')) { originalConsoleError(...messages); } }; return _reactDom.default.findDOMNode(instance); } finally{ console.error = originalConsoleError; } } return _reactDom.default.findDOMNode(instance); } /** * Check if the top of the HTMLElement is in the viewport. */ function topOfElementInViewport(element) { const rect = element.getBoundingClientRect(); return rect.top >= 0; } class ScrollAndFocusHandler extends _react.default.Component { componentDidMount() { // Handle scroll and focus, it's only applied once in the first useEffect that triggers that changed. const { focusAndScrollRef } = this.props; const domNode = findDOMNode(this); if (focusAndScrollRef.apply && domNode instanceof HTMLElement) { // State is mutated to ensure that the focus and scroll is applied only once. focusAndScrollRef.apply = false; // Set focus on the element domNode.focus(); // Only scroll into viewport when the layout is not visible currently. if (!topOfElementInViewport(domNode)) { const htmlElement = document.documentElement; const existing = htmlElement.style.scrollBehavior; htmlElement.style.scrollBehavior = 'auto'; // In Chrome-based browsers we need to force reflow before calling `scrollTo`. // Otherwise it will not pickup the change in scrollBehavior // More info here: https://github.com/vercel/next.js/issues/40719#issuecomment-1336248042 htmlElement.getClientRects(); domNode.scrollIntoView(); htmlElement.style.scrollBehavior = existing; } } } render() { return this.props.children; } } function InnerLayoutRouter({ parallelRouterKey , url , childNodes , childProp , segmentPath , tree , // TODO-APP: implement `` when available. // isActive, path , rootLayoutIncluded }) { const context = (0, _react).useContext(_appRouterContext.GlobalLayoutRouterContext); if (!context) { throw new Error('invariant global layout router not mounted'); } const { changeByServerResponse , tree: fullTree , focusAndScrollRef } = context; // Read segment path from the parallel router cache node. let childNode = childNodes.get(path); // If childProp is available this means it's the Flight / SSR case. if (childProp && // TODO-APP: verify if this can be null based on user code childProp.current !== null) { if (childNode && childNode.status === _appRouterContext.CacheStates.LAZY_INITIALIZED) { // @ts-expect-error TODO-APP: handle changing of the type childNode.status = _appRouterContext.CacheStates.READY; // @ts-expect-error TODO-APP: handle changing of the type childNode.subTreeData = childProp.current; // Mutates the prop in order to clean up the memory associated with the subTreeData as it is now part of the cache. childProp.current = null; } else { // Add the segment's subTreeData to the cache. // This writes to the cache when there is no item in the cache yet. It never *overwrites* existing cache items which is why it's safe in concurrent mode. childNodes.set(path, { status: _appRouterContext.CacheStates.READY, data: null, subTreeData: childProp.current, parallelRoutes: new Map() }); // Mutates the prop in order to clean up the memory associated with the subTreeData as it is now part of the cache. childProp.current = null; // In the above case childNode was set on childNodes, so we have to get it from the cacheNodes again. childNode = childNodes.get(path); } } // When childNode is not available during rendering client-side we need to fetch it from the server. if (!childNode || childNode.status === _appRouterContext.CacheStates.LAZY_INITIALIZED) { /** * Router state with refetch marker added */ // TODO-APP: remove '' const refetchTree = walkAddRefetch([ '', ...segmentPath ], fullTree); /** * Flight data fetch kicked off during render and put into the cache. */ childNodes.set(path, { status: _appRouterContext.CacheStates.DATA_FETCH, data: (0, _appRouter).fetchServerResponse(new URL(url, location.origin), refetchTree), subTreeData: null, head: childNode && childNode.status === _appRouterContext.CacheStates.LAZY_INITIALIZED ? childNode.head : undefined, parallelRoutes: childNode && childNode.status === _appRouterContext.CacheStates.LAZY_INITIALIZED ? childNode.parallelRoutes : new Map() }); // In the above case childNode was set on childNodes, so we have to get it from the cacheNodes again. childNode = childNodes.get(path); } // This case should never happen so it throws an error. It indicates there's a bug in the Next.js. if (!childNode) { throw new Error('Child node should always exist'); } // This case should never happen so it throws an error. It indicates there's a bug in the Next.js. if (childNode.subTreeData && childNode.data) { throw new Error('Child node should not have both subTreeData and data'); } // If cache node has a data request we have to unwrap response by `use` and update the cache. if (childNode.data) { /** * Flight response data */ // When the data has not resolved yet `use` will suspend here. const [flightData, overrideCanonicalUrl] = (0, _react).use(childNode.data); // Handle case when navigating to page in `pages` from `app` if (typeof flightData === 'string') { window.location.href = url; return null; } // segmentPath from the server does not match the layout's segmentPath childNode.data = null; // setTimeout is used to start a new transition during render, this is an intentional hack around React. setTimeout(()=>{ // @ts-ignore startTransition exists _react.default.startTransition(()=>{ changeByServerResponse(fullTree, flightData, overrideCanonicalUrl); }); }); // Suspend infinitely as `changeByServerResponse` will cause a different part of the tree to be rendered. (0, _react).use((0, _infinitePromise).createInfinitePromise()); } // If cache node has no subTreeData and no data request we have to infinitely suspend as the data will likely flow in from another place. // TODO-APP: double check users can't return null in a component that will kick in here. if (!childNode.subTreeData) { (0, _react).use((0, _infinitePromise).createInfinitePromise()); } const subtree = // The layout router context narrows down tree and childNodes at each level. /*#__PURE__*/ _react.default.createElement(_appRouterContext.LayoutRouterContext.Provider, { value: { tree: tree[1][parallelRouterKey], childNodes: childNode.parallelRoutes, // TODO-APP: overriding of url for parallel routes url: url } }, childNode.subTreeData); // Ensure root layout is not wrapped in a div as the root layout renders `` return rootLayoutIncluded ? /*#__PURE__*/ _react.default.createElement(ScrollAndFocusHandler, { focusAndScrollRef: focusAndScrollRef }, subtree) : subtree; } /** * Renders suspense boundary with the provided "loading" property as the fallback. * If no loading property is provided it renders the children without a suspense boundary. */ function LoadingBoundary({ children , loading , loadingStyles , hasLoading }) { if (hasLoading) { return /*#__PURE__*/ _react.default.createElement(_react.default.Suspense, { fallback: /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, loadingStyles, loading) }, children); } return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, children); } function HandleRedirect({ redirect }) { const router = (0, _navigation).useRouter(); (0, _react).useEffect(()=>{ router.replace(redirect, {}); }, [ redirect, router ]); return null; } class RedirectErrorBoundary extends _react.default.Component { static getDerivedStateFromError(error) { var ref; if (error == null ? void 0 : (ref = error.digest) == null ? void 0 : ref.startsWith('NEXT_REDIRECT')) { const url = error.digest.split(';')[1]; return { redirect: url }; } // Re-throw if error is not for redirect throw error; } render() { const redirect = this.state.redirect; if (redirect !== null) { return /*#__PURE__*/ _react.default.createElement(HandleRedirect, { redirect: redirect }); } return this.props.children; } constructor(props){ super(props); this.state = { redirect: null }; } } function RedirectBoundary({ children }) { const router = (0, _navigation).useRouter(); return /*#__PURE__*/ _react.default.createElement(RedirectErrorBoundary, { router: router }, children); } class NotFoundErrorBoundary extends _react.default.Component { static getDerivedStateFromError(error) { if ((error == null ? void 0 : error.digest) === 'NEXT_NOT_FOUND') { return { notFoundTriggered: true }; } // Re-throw if error is not for 404 throw error; } render() { if (this.state.notFoundTriggered) { return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/ _react.default.createElement("meta", { name: "robots", content: "noindex" }), this.props.notFoundStyles, this.props.notFound); } return this.props.children; } constructor(props){ super(props); this.state = { notFoundTriggered: false }; } } function NotFoundBoundary({ notFound , notFoundStyles , children }) { return notFound ? /*#__PURE__*/ _react.default.createElement(NotFoundErrorBoundary, { notFound: notFound, notFoundStyles: notFoundStyles }, children) : /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, children); } if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') { Object.defineProperty(exports.default, '__esModule', { value: true }); Object.assign(exports.default, exports); module.exports = exports.default; } //# sourceMappingURL=layout-router.js.map