663 lines
33 KiB
JavaScript
663 lines
33 KiB
JavaScript
'use client';
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
0 && (module.exports = {
|
|
LoadingBoundaryProvider: null,
|
|
default: null
|
|
});
|
|
function _export(target, all) {
|
|
for(var name in all)Object.defineProperty(target, name, {
|
|
enumerable: true,
|
|
get: all[name]
|
|
});
|
|
}
|
|
_export(exports, {
|
|
LoadingBoundaryProvider: function() {
|
|
return LoadingBoundaryProvider;
|
|
},
|
|
/**
|
|
* OuterLayoutRouter handles the current segment as well as <Offscreen> rendering of other segments.
|
|
* It can be rendered next to each other with a different `parallelRouterKey`, allowing for Parallel routes.
|
|
*/ default: function() {
|
|
return OuterLayoutRouter;
|
|
}
|
|
});
|
|
const _interop_require_default = require("@swc/helpers/_/_interop_require_default");
|
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
|
const _jsxruntime = require("react/jsx-runtime");
|
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
|
const _reactdom = /*#__PURE__*/ _interop_require_default._(require("react-dom"));
|
|
const _approutercontextsharedruntime = require("../../shared/lib/app-router-context.shared-runtime");
|
|
const _unresolvedthenable = require("./unresolved-thenable");
|
|
const _errorboundary = require("./error-boundary");
|
|
const _disablesmoothscroll = require("../../shared/lib/router/utils/disable-smooth-scroll");
|
|
const _redirectboundary = require("./redirect-boundary");
|
|
const _errorboundary1 = require("./http-access-fallback/error-boundary");
|
|
const _createroutercachekey = require("./router-reducer/create-router-cache-key");
|
|
const _bfcachestatemanager = require("./bfcache-state-manager");
|
|
const _apppaths = require("../../shared/lib/router/utils/app-paths");
|
|
const _hooksclientcontextsharedruntime = require("../../shared/lib/hooks-client-context.shared-runtime");
|
|
const _routeparams = require("../route-params");
|
|
const _pprnavigations = require("./router-reducer/ppr-navigations");
|
|
const enableNewScrollHandler = process.env.__NEXT_APP_NEW_SCROLL_HANDLER;
|
|
const __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = _reactdom.default.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
// 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;
|
|
// __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode is null during module init.
|
|
// We need to lazily reference it.
|
|
const internal_reactDOMfindDOMNode = __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.findDOMNode;
|
|
return internal_reactDOMfindDOMNode(instance);
|
|
}
|
|
const rectProperties = [
|
|
'bottom',
|
|
'height',
|
|
'left',
|
|
'right',
|
|
'top',
|
|
'width',
|
|
'x',
|
|
'y'
|
|
];
|
|
/**
|
|
* Check if a HTMLElement is hidden or fixed/sticky position
|
|
*/ function shouldSkipElement(element) {
|
|
// we ignore fixed or sticky positioned elements since they'll likely pass the "in-viewport" check
|
|
// and will result in a situation we bail on scroll because of something like a fixed nav,
|
|
// even though the actual page content is offscreen
|
|
if ([
|
|
'sticky',
|
|
'fixed'
|
|
].includes(getComputedStyle(element).position)) {
|
|
return true;
|
|
}
|
|
// Uses `getBoundingClientRect` to check if the element is hidden instead of `offsetParent`
|
|
// because `offsetParent` doesn't consider document/body
|
|
const rect = element.getBoundingClientRect();
|
|
return rectProperties.every((item)=>rect[item] === 0);
|
|
}
|
|
/**
|
|
* Check if the top corner of the HTMLElement is in the viewport.
|
|
*/ function topOfElementInViewport(instance, viewportHeight) {
|
|
const rects = instance.getClientRects();
|
|
if (rects.length === 0) {
|
|
// Just to be explicit.
|
|
return false;
|
|
}
|
|
let elementTop = Number.POSITIVE_INFINITY;
|
|
for(let i = 0; i < rects.length; i++){
|
|
const rect = rects[i];
|
|
if (rect.top < elementTop) {
|
|
elementTop = rect.top;
|
|
}
|
|
}
|
|
return elementTop >= 0 && elementTop <= viewportHeight;
|
|
}
|
|
/**
|
|
* Find the DOM node for a hash fragment.
|
|
* If `top` the page has to scroll to the top of the page. This mirrors the browser's behavior.
|
|
* If the hash fragment is an id, the page has to scroll to the element with that id.
|
|
* If the hash fragment is a name, the page has to scroll to the first element with that name.
|
|
*/ function getHashFragmentDomNode(hashFragment) {
|
|
// If the hash fragment is `top` the page has to scroll to the top of the page.
|
|
if (hashFragment === 'top') {
|
|
return document.body;
|
|
}
|
|
// If the hash fragment is an id, the page has to scroll to the element with that id.
|
|
return document.getElementById(hashFragment) ?? // If the hash fragment is a name, the page has to scroll to the first element with that name.
|
|
document.getElementsByName(hashFragment)[0];
|
|
}
|
|
class InnerScrollAndFocusHandlerOld extends _react.default.Component {
|
|
componentDidMount() {
|
|
this.handlePotentialScroll();
|
|
}
|
|
componentDidUpdate() {
|
|
this.handlePotentialScroll();
|
|
}
|
|
render() {
|
|
return this.props.children;
|
|
}
|
|
constructor(...args){
|
|
super(...args), this.handlePotentialScroll = ()=>{
|
|
// Handle scroll and focus, it's only applied once.
|
|
const { focusAndScrollRef, cacheNode } = this.props;
|
|
const scrollRef = focusAndScrollRef.forceScroll ? focusAndScrollRef.scrollRef : cacheNode.scrollRef;
|
|
if (scrollRef === null || !scrollRef.current) return;
|
|
let domNode = null;
|
|
const hashFragment = focusAndScrollRef.hashFragment;
|
|
if (hashFragment) {
|
|
domNode = getHashFragmentDomNode(hashFragment);
|
|
}
|
|
// `findDOMNode` is tricky because it returns just the first child if the component is a fragment.
|
|
// This already caused a bug where the first child was a <link/> in head.
|
|
if (!domNode) {
|
|
domNode = findDOMNode(this);
|
|
}
|
|
// If there is no DOM node this layout-router level is skipped. It'll be handled higher-up in the tree.
|
|
if (!(domNode instanceof Element)) {
|
|
return;
|
|
}
|
|
// Verify if the element is a HTMLElement and if we want to consider it for scroll behavior.
|
|
// If the element is skipped, try to select the next sibling and try again.
|
|
while(!(domNode instanceof HTMLElement) || shouldSkipElement(domNode)){
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (domNode.parentElement?.localName === 'head') {
|
|
// We enter this state when metadata was rendered as part of the page or via Next.js.
|
|
// This is always a bug in Next.js and caused by React hoisting metadata.
|
|
// Fixed with `experimental.appNewScrollHandler`
|
|
}
|
|
}
|
|
// No siblings found that match the criteria are found, so handle scroll higher up in the tree instead.
|
|
if (domNode.nextElementSibling === null) {
|
|
return;
|
|
}
|
|
domNode = domNode.nextElementSibling;
|
|
}
|
|
// Mark as scrolled so no other segment scrolls for this navigation.
|
|
scrollRef.current = false;
|
|
(0, _disablesmoothscroll.disableSmoothScrollDuringRouteTransition)(()=>{
|
|
// In case of hash scroll, we only need to scroll the element into view
|
|
if (hashFragment) {
|
|
domNode.scrollIntoView();
|
|
return;
|
|
}
|
|
// Store the current viewport height because reading `clientHeight` causes a reflow,
|
|
// and it won't change during this function.
|
|
const htmlElement = document.documentElement;
|
|
const viewportHeight = htmlElement.clientHeight;
|
|
// If the element's top edge is already in the viewport, exit early.
|
|
if (topOfElementInViewport(domNode, viewportHeight)) {
|
|
return;
|
|
}
|
|
// Otherwise, try scrolling go the top of the document to be backward compatible with pages
|
|
// scrollIntoView() called on `<html/>` element scrolls horizontally on chrome and firefox (that shouldn't happen)
|
|
// We could use it to scroll horizontally following RTL but that also seems to be broken - it will always scroll left
|
|
// scrollLeft = 0 also seems to ignore RTL and manually checking for RTL is too much hassle so we will scroll just vertically
|
|
htmlElement.scrollTop = 0;
|
|
// Scroll to domNode if domNode is not in viewport when scrolled to top of document
|
|
if (!topOfElementInViewport(domNode, viewportHeight)) {
|
|
// Scroll into view doesn't scroll horizontally by default when not needed
|
|
domNode.scrollIntoView();
|
|
}
|
|
}, {
|
|
// We will force layout by querying domNode position
|
|
dontForceLayout: true,
|
|
onlyHashChange: focusAndScrollRef.onlyHashChange
|
|
});
|
|
// Mutate after scrolling so that it can be read by `disableSmoothScrollDuringRouteTransition`
|
|
focusAndScrollRef.onlyHashChange = false;
|
|
focusAndScrollRef.hashFragment = null;
|
|
// Set focus on the element
|
|
domNode.focus();
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Fork of InnerScrollAndFocusHandlerOld using Fragment refs for scrolling.
|
|
* No longer focuses the first host descendant.
|
|
*/ function InnerScrollHandlerNew(props) {
|
|
const childrenRef = _react.default.useRef(null);
|
|
(0, _react.useLayoutEffect)(()=>{
|
|
const { focusAndScrollRef, cacheNode } = props;
|
|
const scrollRef = focusAndScrollRef.forceScroll ? focusAndScrollRef.scrollRef : cacheNode.scrollRef;
|
|
if (scrollRef === null || !scrollRef.current) return;
|
|
let instance = null;
|
|
const hashFragment = focusAndScrollRef.hashFragment;
|
|
if (hashFragment) {
|
|
instance = getHashFragmentDomNode(hashFragment);
|
|
}
|
|
if (!instance) {
|
|
instance = childrenRef.current;
|
|
}
|
|
// If there is no DOM node this layout-router level is skipped. It'll be handled higher-up in the tree.
|
|
if (instance === null) {
|
|
return;
|
|
}
|
|
// Mark as scrolled so no other segment scrolls for this navigation.
|
|
scrollRef.current = false;
|
|
const activeElement = document.activeElement;
|
|
if (activeElement !== null && 'blur' in activeElement && typeof activeElement.blur === 'function') {
|
|
// Trying to match hard navigations.
|
|
// Ideally we'd move the internal focus cursor either to the top
|
|
// or at least before the segment. But there's no DOM API to do that,
|
|
// so we just blur.
|
|
// We could workaround this by moving focus to a temporary element in
|
|
// the body. But adding elements might trigger layout or other effects
|
|
// so it should be well motivated.
|
|
activeElement.blur();
|
|
}
|
|
(0, _disablesmoothscroll.disableSmoothScrollDuringRouteTransition)(()=>{
|
|
// In case of hash scroll, we only need to scroll the element into view
|
|
if (hashFragment) {
|
|
instance.scrollIntoView();
|
|
return;
|
|
}
|
|
// Store the current viewport height because reading `clientHeight` causes a reflow,
|
|
// and it won't change during this function.
|
|
const htmlElement = document.documentElement;
|
|
const viewportHeight = htmlElement.clientHeight;
|
|
// If the element's top edge is already in the viewport, exit early.
|
|
if (topOfElementInViewport(instance, viewportHeight)) {
|
|
return;
|
|
}
|
|
// Otherwise, try scrolling go the top of the document to be backward compatible with pages
|
|
// scrollIntoView() called on `<html/>` element scrolls horizontally on chrome and firefox (that shouldn't happen)
|
|
// We could use it to scroll horizontally following RTL but that also seems to be broken - it will always scroll left
|
|
// scrollLeft = 0 also seems to ignore RTL and manually checking for RTL is too much hassle so we will scroll just vertically
|
|
htmlElement.scrollTop = 0;
|
|
// Scroll to domNode if domNode is not in viewport when scrolled to top of document
|
|
if (!topOfElementInViewport(instance, viewportHeight)) {
|
|
// Scroll into view doesn't scroll horizontally by default when not needed
|
|
instance.scrollIntoView();
|
|
}
|
|
}, {
|
|
// We will force layout by querying domNode position
|
|
dontForceLayout: true,
|
|
onlyHashChange: focusAndScrollRef.onlyHashChange
|
|
});
|
|
// Mutate after scrolling so that it can be read by `disableSmoothScrollDuringRouteTransition`
|
|
focusAndScrollRef.onlyHashChange = false;
|
|
focusAndScrollRef.hashFragment = null;
|
|
}, // Used to run on every commit. We may be able to be smarter about this
|
|
// but be prepared for lots of manual testing.
|
|
undefined);
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Fragment, {
|
|
ref: childrenRef,
|
|
children: props.children
|
|
});
|
|
}
|
|
const InnerScrollAndMaybeFocusHandler = enableNewScrollHandler ? InnerScrollHandlerNew : InnerScrollAndFocusHandlerOld;
|
|
function ScrollAndMaybeFocusHandler({ children, cacheNode }) {
|
|
const context = (0, _react.useContext)(_approutercontextsharedruntime.GlobalLayoutRouterContext);
|
|
if (!context) {
|
|
throw Object.defineProperty(new Error('invariant global layout router not mounted'), "__NEXT_ERROR_CODE", {
|
|
value: "E473",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(InnerScrollAndMaybeFocusHandler, {
|
|
focusAndScrollRef: context.focusAndScrollRef,
|
|
cacheNode: cacheNode,
|
|
children: children
|
|
});
|
|
}
|
|
/**
|
|
* InnerLayoutRouter handles rendering the provided segment based on the cache.
|
|
*/ function InnerLayoutRouter({ tree, segmentPath, debugNameContext, cacheNode: maybeCacheNode, params, url, isActive }) {
|
|
const context = (0, _react.useContext)(_approutercontextsharedruntime.GlobalLayoutRouterContext);
|
|
const parentNavPromises = (0, _react.useContext)(_hooksclientcontextsharedruntime.NavigationPromisesContext);
|
|
if (!context) {
|
|
throw Object.defineProperty(new Error('invariant global layout router not mounted'), "__NEXT_ERROR_CODE", {
|
|
value: "E473",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
const cacheNode = maybeCacheNode !== null ? maybeCacheNode : //
|
|
// This should only be reachable for inactive/hidden segments, during
|
|
// prerendering The active segment should always be consistent with the
|
|
// CacheNode tree. Regardless, if we don't have a matching CacheNode, we
|
|
// must suspend rather than render nothing, to prevent showing an
|
|
// inconsistent route.
|
|
(0, _react.use)(_unresolvedthenable.unresolvedThenable);
|
|
// `rsc` represents the renderable node for this segment.
|
|
// If this segment has a `prefetchRsc`, it's the statically prefetched data.
|
|
// We should use that on initial render instead of `rsc`. Then we'll switch
|
|
// to `rsc` when the dynamic response streams in.
|
|
//
|
|
// If no prefetch data is available, then we go straight to rendering `rsc`.
|
|
const resolvedPrefetchRsc = cacheNode.prefetchRsc !== null ? cacheNode.prefetchRsc : cacheNode.rsc;
|
|
// We use `useDeferredValue` to handle switching between the prefetched and
|
|
// final values. The second argument is returned on initial render, then it
|
|
// re-renders with the first argument.
|
|
const rsc = (0, _react.useDeferredValue)(cacheNode.rsc, resolvedPrefetchRsc);
|
|
// `rsc` is either a React node or a promise for a React node, except we
|
|
// special case `null` to represent that this segment's data is missing. If
|
|
// it's a promise, we need to unwrap it so we can determine whether or not the
|
|
// data is missing.
|
|
let resolvedRsc;
|
|
if ((0, _pprnavigations.isDeferredRsc)(rsc)) {
|
|
const unwrappedRsc = (0, _react.use)(rsc);
|
|
if (unwrappedRsc === null) {
|
|
// If the promise was resolved to `null`, it means the data for this
|
|
// segment was not returned by the server. Suspend indefinitely. When this
|
|
// happens, the router is responsible for triggering a new state update to
|
|
// un-suspend this segment.
|
|
(0, _react.use)(_unresolvedthenable.unresolvedThenable);
|
|
}
|
|
resolvedRsc = unwrappedRsc;
|
|
} else {
|
|
// This is not a deferred RSC promise. Don't need to unwrap it.
|
|
if (rsc === null) {
|
|
(0, _react.use)(_unresolvedthenable.unresolvedThenable);
|
|
}
|
|
resolvedRsc = rsc;
|
|
}
|
|
// In dev, we create a NavigationPromisesContext containing the instrumented promises that provide
|
|
// `useSelectedLayoutSegment` and `useSelectedLayoutSegments`.
|
|
// Promises are cached outside of render to survive suspense retries.
|
|
let navigationPromises = null;
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
const { createNestedLayoutNavigationPromises } = require('./navigation-devtools');
|
|
navigationPromises = createNestedLayoutNavigationPromises(tree, parentNavPromises);
|
|
}
|
|
let children = resolvedRsc;
|
|
if (navigationPromises) {
|
|
children = /*#__PURE__*/ (0, _jsxruntime.jsx)(_hooksclientcontextsharedruntime.NavigationPromisesContext.Provider, {
|
|
value: navigationPromises,
|
|
children: resolvedRsc
|
|
});
|
|
}
|
|
children = // The layout router context narrows down tree and childNodes at each level.
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(_approutercontextsharedruntime.LayoutRouterContext.Provider, {
|
|
value: {
|
|
parentTree: tree,
|
|
parentCacheNode: cacheNode,
|
|
parentSegmentPath: segmentPath,
|
|
parentParams: params,
|
|
// This is always set to null as we enter a child segment. It's
|
|
// populated by LoadingBoundaryProvider the next time we reach a
|
|
// loading boundary.
|
|
parentLoadingData: null,
|
|
debugNameContext: debugNameContext,
|
|
// TODO-APP: overriding of url for parallel routes
|
|
url: url,
|
|
isActive: isActive
|
|
},
|
|
children: children
|
|
});
|
|
return children;
|
|
}
|
|
function LoadingBoundaryProvider({ loading, children }) {
|
|
// Provides the data needed to render a loading.tsx boundary, via context.
|
|
//
|
|
// loading.tsx creates a Suspense boundary around each of a layout's child
|
|
// slots. (Might be bit confusing to think about the data flow, but: if
|
|
// loading.tsx and layout.tsx are in the same directory, they are assigned
|
|
// to the same CacheNode.)
|
|
//
|
|
// This provider component does not render the Suspense boundary directly;
|
|
// that's handled by LoadingBoundary.
|
|
//
|
|
// TODO: For simplicity, we should combine this provider with LoadingBoundary
|
|
// and render the Suspense boundary directly. The only real benefit of doing
|
|
// it separately is so that when there are multiple parallel routes, we only
|
|
// send the boundary data once, rather than once per child. But that's a
|
|
// negligible benefit and can be achieved via caching instead.
|
|
const parentContext = (0, _react.use)(_approutercontextsharedruntime.LayoutRouterContext);
|
|
if (parentContext === null) {
|
|
return children;
|
|
}
|
|
// All values except for parentLoadingData are the same as the parent context.
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_approutercontextsharedruntime.LayoutRouterContext.Provider, {
|
|
value: {
|
|
parentTree: parentContext.parentTree,
|
|
parentCacheNode: parentContext.parentCacheNode,
|
|
parentSegmentPath: parentContext.parentSegmentPath,
|
|
parentParams: parentContext.parentParams,
|
|
parentLoadingData: loading,
|
|
debugNameContext: parentContext.debugNameContext,
|
|
url: parentContext.url,
|
|
isActive: parentContext.isActive
|
|
},
|
|
children: children
|
|
});
|
|
}
|
|
/**
|
|
* 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({ name, loading, children }) {
|
|
// TODO: For LoadingBoundary, and the other built-in boundary types, don't
|
|
// wrap in an extra function component if no user-defined boundary is
|
|
// provided. In other words, inline this conditional wrapping logic into
|
|
// the parent component. More efficient and keeps unnecessary junk out of
|
|
// the component stack.
|
|
if (loading !== null) {
|
|
const loadingRsc = loading[0];
|
|
const loadingStyles = loading[1];
|
|
const loadingScripts = loading[2];
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Suspense, {
|
|
name: name,
|
|
fallback: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
|
|
children: [
|
|
loadingStyles,
|
|
loadingScripts,
|
|
loadingRsc
|
|
]
|
|
}),
|
|
children: children
|
|
});
|
|
}
|
|
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
|
|
children: children
|
|
});
|
|
}
|
|
function OuterLayoutRouter({ parallelRouterKey, error, errorStyles, errorScripts, templateStyles, templateScripts, template, notFound, forbidden, unauthorized, segmentViewBoundaries }) {
|
|
const context = (0, _react.useContext)(_approutercontextsharedruntime.LayoutRouterContext);
|
|
if (!context) {
|
|
throw Object.defineProperty(new Error('invariant expected layout router to be mounted'), "__NEXT_ERROR_CODE", {
|
|
value: "E56",
|
|
enumerable: false,
|
|
configurable: true
|
|
});
|
|
}
|
|
const { parentTree, parentCacheNode, parentSegmentPath, parentParams, parentLoadingData, url, isActive, debugNameContext } = context;
|
|
// Get the CacheNode for this segment by reading it from the parent segment's
|
|
// child map.
|
|
const parentTreeSegment = parentTree[0];
|
|
const segmentPath = parentSegmentPath === null ? // path. This has led to a bunch of special cases scattered throughout
|
|
// the code. We should clean this up.
|
|
[
|
|
parallelRouterKey
|
|
] : parentSegmentPath.concat([
|
|
parentTreeSegment,
|
|
parallelRouterKey
|
|
]);
|
|
// The "state" key of a segment is the one passed to React — it represents the
|
|
// identity of the UI tree. Whenever the state key changes, the tree is
|
|
// recreated and the state is reset. In the App Router model, search params do
|
|
// not cause state to be lost, so two segments with the same segment path but
|
|
// different search params should have the same state key.
|
|
//
|
|
// The "cache" key of a segment, however, *does* include the search params, if
|
|
// it's possible that the segment accessed the search params on the server.
|
|
// (This only applies to page segments; layout segments cannot access search
|
|
// params on the server.)
|
|
const activeTree = parentTree[1][parallelRouterKey];
|
|
const maybeParentSlots = parentCacheNode.slots;
|
|
if (activeTree === undefined || maybeParentSlots === null) {
|
|
// Could not find a matching segment. The client tree is inconsistent with
|
|
// the server tree. Suspend indefinitely; the router will have already
|
|
// detected the inconsistency when handling the server response, and
|
|
// triggered a refresh of the page to recover.
|
|
(0, _react.use)(_unresolvedthenable.unresolvedThenable);
|
|
}
|
|
let maybeValidationBoundaryId = null;
|
|
if (typeof window === 'undefined' && process.env.__NEXT_CACHE_COMPONENTS) {
|
|
const { InstantValidationBoundaryContext } = require('./instant-validation/boundary');
|
|
maybeValidationBoundaryId = (0, _react.use)(InstantValidationBoundaryContext);
|
|
}
|
|
const activeSegment = activeTree[0];
|
|
const activeCacheNode = maybeParentSlots[parallelRouterKey] ?? null;
|
|
const activeStateKey = (0, _createroutercachekey.createRouterCacheKey)(activeSegment, true) // no search params
|
|
;
|
|
// At each level of the route tree, not only do we render the currently
|
|
// active segment — we also render the last N segments that were active at
|
|
// this level inside a hidden <Activity> boundary, to preserve their state
|
|
// if or when the user navigates to them again.
|
|
//
|
|
// bfcacheEntry is a linked list of FlightRouterStates.
|
|
let bfcacheEntry = (0, _bfcachestatemanager.useRouterBFCache)(activeTree, activeCacheNode, activeStateKey);
|
|
let children = [];
|
|
do {
|
|
const tree = bfcacheEntry.tree;
|
|
const cacheNode = bfcacheEntry.cacheNode;
|
|
const stateKey = bfcacheEntry.stateKey;
|
|
const segment = tree[0];
|
|
/*
|
|
- Error boundary
|
|
- Only renders error boundary if error component is provided.
|
|
- Rendered for each segment to ensure they have their own error state.
|
|
- When gracefully degrade for bots, skip rendering error boundary.
|
|
- 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.
|
|
*/ let segmentBoundaryTriggerNode = null;
|
|
let segmentViewStateNode = null;
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
const { SegmentBoundaryTriggerNode, SegmentViewStateNode } = require('../../next-devtools/userspace/app/segment-explorer-node');
|
|
const pagePrefix = (0, _apppaths.normalizeAppPath)(url);
|
|
segmentViewStateNode = /*#__PURE__*/ (0, _jsxruntime.jsx)(SegmentViewStateNode, {
|
|
page: pagePrefix
|
|
}, pagePrefix);
|
|
segmentBoundaryTriggerNode = /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(SegmentBoundaryTriggerNode, {})
|
|
});
|
|
}
|
|
let params = parentParams;
|
|
if (Array.isArray(segment)) {
|
|
// This segment contains a route param. Accumulate these as we traverse
|
|
// down the router tree. The result represents the set of params that
|
|
// the layout/page components are permitted to access below this point.
|
|
const paramName = segment[0];
|
|
const paramCacheKey = segment[1];
|
|
const paramType = segment[2];
|
|
const paramValue = (0, _routeparams.getParamValueFromCacheKey)(paramCacheKey, paramType);
|
|
if (paramValue !== null) {
|
|
params = {
|
|
...parentParams,
|
|
[paramName]: paramValue
|
|
};
|
|
}
|
|
}
|
|
const debugName = getBoundaryDebugNameFromSegment(segment);
|
|
// `debugNameContext` represents the nearest non-"virtual" parent segment.
|
|
// `getBoundaryDebugNameFromSegment` returns undefined for virtual segments.
|
|
// So if `debugName` is undefined, the context is passed through unchanged.
|
|
const childDebugNameContext = debugName ?? debugNameContext;
|
|
// In practical terms, clicking this name in the Suspense DevTools
|
|
// should select the child slots of that layout.
|
|
//
|
|
// So the name we apply to the Activity boundary is actually based on
|
|
// the nearest parent segments.
|
|
//
|
|
// We skip over "virtual" parents, i.e. ones inserted by Next.js that
|
|
// don't correspond to application-defined code.
|
|
const isVirtual = debugName === undefined;
|
|
const debugNameToDisplay = isVirtual ? undefined : debugNameContext;
|
|
let templateValue = /*#__PURE__*/ (0, _jsxruntime.jsxs)(ScrollAndMaybeFocusHandler, {
|
|
cacheNode: cacheNode,
|
|
children: [
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(_errorboundary.ErrorBoundary, {
|
|
errorComponent: error,
|
|
errorStyles: errorStyles,
|
|
errorScripts: errorScripts,
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(LoadingBoundary, {
|
|
name: debugNameToDisplay,
|
|
// TODO: The loading module data for a segment is stored on the
|
|
// parent, then applied to each of that parent segment's
|
|
// parallel route slots. In the simple case where there's only
|
|
// one parallel route (the `children` slot), this is no
|
|
// different from if the loading module data were stored on the
|
|
// child directly. But I'm not sure this actually makes sense
|
|
// when there are multiple parallel routes. It's not a huge
|
|
// issue because you always have the option to define a narrower
|
|
// loading boundary for a particular slot. But this sort of
|
|
// smells like an implementation accident to me.
|
|
loading: parentLoadingData,
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_errorboundary1.HTTPAccessFallbackBoundary, {
|
|
notFound: notFound,
|
|
forbidden: forbidden,
|
|
unauthorized: unauthorized,
|
|
children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_redirectboundary.RedirectBoundary, {
|
|
children: [
|
|
/*#__PURE__*/ (0, _jsxruntime.jsx)(InnerLayoutRouter, {
|
|
url: url,
|
|
tree: tree,
|
|
params: params,
|
|
cacheNode: cacheNode,
|
|
segmentPath: segmentPath,
|
|
debugNameContext: childDebugNameContext,
|
|
isActive: isActive && stateKey === activeStateKey
|
|
}),
|
|
segmentBoundaryTriggerNode
|
|
]
|
|
})
|
|
})
|
|
})
|
|
}),
|
|
segmentViewStateNode
|
|
]
|
|
});
|
|
if (typeof window === 'undefined' && process.env.__NEXT_CACHE_COMPONENTS && typeof maybeValidationBoundaryId === 'string') {
|
|
const { RenderValidationBoundaryAtThisLevel } = require('./instant-validation/boundary');
|
|
templateValue = /*#__PURE__*/ (0, _jsxruntime.jsx)(RenderValidationBoundaryAtThisLevel, {
|
|
id: maybeValidationBoundaryId,
|
|
children: templateValue
|
|
});
|
|
}
|
|
let child = /*#__PURE__*/ (0, _jsxruntime.jsxs)(_approutercontextsharedruntime.TemplateContext.Provider, {
|
|
value: templateValue,
|
|
children: [
|
|
templateStyles,
|
|
templateScripts,
|
|
template
|
|
]
|
|
}, stateKey);
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
const { SegmentStateProvider } = require('../../next-devtools/userspace/app/segment-explorer-node');
|
|
child = /*#__PURE__*/ (0, _jsxruntime.jsxs)(SegmentStateProvider, {
|
|
children: [
|
|
child,
|
|
segmentViewBoundaries
|
|
]
|
|
}, stateKey);
|
|
}
|
|
if (process.env.__NEXT_CACHE_COMPONENTS) {
|
|
child = /*#__PURE__*/ (0, _jsxruntime.jsx)(_react.Activity, {
|
|
name: debugNameToDisplay,
|
|
mode: stateKey === activeStateKey ? 'visible' : 'hidden',
|
|
children: child
|
|
}, stateKey);
|
|
}
|
|
children.push(child);
|
|
bfcacheEntry = bfcacheEntry.next;
|
|
}while (bfcacheEntry !== null);
|
|
return children;
|
|
}
|
|
function getBoundaryDebugNameFromSegment(segment) {
|
|
if (segment === '/') {
|
|
// Reached the root
|
|
return '/';
|
|
}
|
|
if (typeof segment === 'string') {
|
|
if (isVirtualLayout(segment)) {
|
|
return undefined;
|
|
} else {
|
|
return segment + '/';
|
|
}
|
|
}
|
|
const paramCacheKey = segment[1];
|
|
return paramCacheKey + '/';
|
|
}
|
|
function isVirtualLayout(segment) {
|
|
return(// This is inserted by the loader. Uses double-underscore convention
|
|
// (like __PAGE__ and __DEFAULT__) to avoid collisions with
|
|
// user-defined route groups.
|
|
segment === '(__SLOT__)');
|
|
}
|
|
|
|
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
|