import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, shallowReactive, shallowRef, unref } from "vue"; import { useRoute, useRouter } from "../composables/router.js"; import { useNuxtApp } from "../nuxt.js"; import { _wrapInTransition } from "./utils.js"; import { LayoutMetaSymbol, PageRouteSymbol } from "./injections.js"; import { useRoute as useVueRouterRoute } from "#build/pages"; import layouts from "#build/layouts"; import { appLayoutTransition as defaultLayoutTransition } from "#build/nuxt.config.mjs"; const LayoutLoader = defineComponent({ name: "LayoutLoader", inheritAttrs: false, props: { name: String, layoutProps: Object }, setup(props, context) { return () => h(layouts[props.name], props.layoutProps, context.slots); } }); const nuxtLayoutProps = { name: { type: [String, Boolean, Object], default: null }, fallback: { type: [String, Object], default: null } }; export default defineComponent({ name: "NuxtLayout", inheritAttrs: false, props: nuxtLayoutProps, setup(props, context) { const nuxtApp = useNuxtApp(); const injectedRoute = inject(PageRouteSymbol); const shouldUseEagerRoute = !injectedRoute || injectedRoute === useRoute(); const route = shouldUseEagerRoute ? useVueRouterRoute() : injectedRoute; const layout = computed(() => { let layout2 = unref(props.name) ?? route?.meta.layout ?? "default"; if (layout2 && !(layout2 in layouts)) { if (import.meta.dev && layout2 !== "default") { console.warn(`Invalid layout \`${layout2}\` selected.`); } if (props.fallback) { layout2 = unref(props.fallback); } } return layout2; }); const layoutRef = shallowRef(); context.expose({ layoutRef }); const done = nuxtApp.deferHydration(); if (import.meta.client && nuxtApp.isHydrating) { const removeErrorHook = nuxtApp.hooks.hookOnce("app:error", done); useRouter().beforeEach(removeErrorHook); } if (import.meta.dev) { nuxtApp._isNuxtLayoutUsed = true; } let lastLayout; return () => { const hasLayout = layout.value && layout.value in layouts; const transitionProps = route?.meta.layoutTransition ?? defaultLayoutTransition; const previouslyRenderedLayout = lastLayout; lastLayout = layout.value; return _wrapInTransition(hasLayout && transitionProps, { default: () => h(Suspense, { suspensible: true, onResolve: () => { nextTick(done); } }, { default: () => h( LayoutProvider, { layoutProps: mergeProps(context.attrs, { ref: layoutRef }), key: layout.value || void 0, name: layout.value, shouldProvide: !props.name, isRenderingNewLayout: (name) => { return name !== previouslyRenderedLayout && name === layout.value; }, hasTransition: !!transitionProps }, context.slots ) }) }).default(); }; } }); const LayoutProvider = defineComponent({ name: "NuxtLayoutProvider", inheritAttrs: false, props: { name: { type: [String, Boolean] }, layoutProps: { type: Object }, hasTransition: { type: Boolean }, shouldProvide: { type: Boolean }, isRenderingNewLayout: { type: Function, required: true } }, setup(props, context) { const name = props.name; if (props.shouldProvide) { provide(LayoutMetaSymbol, { isCurrent: (route) => name === (route.meta.layout ?? "default") }); } const injectedRoute = inject(PageRouteSymbol); const isNotWithinNuxtPage = injectedRoute && injectedRoute === useRoute(); if (isNotWithinNuxtPage) { const vueRouterRoute = useVueRouterRoute(); const reactiveChildRoute = {}; for (const _key in vueRouterRoute) { const key = _key; Object.defineProperty(reactiveChildRoute, key, { enumerable: true, get: () => { return props.isRenderingNewLayout(props.name) ? vueRouterRoute[key] : injectedRoute[key]; } }); } provide(PageRouteSymbol, shallowReactive(reactiveChildRoute)); } let vnode; if (import.meta.dev && import.meta.client) { onMounted(() => { nextTick(() => { if (["#comment", "#text"].includes(vnode?.el?.nodeName)) { if (name) { console.warn(`[nuxt] \`${name}\` layout does not have a single root node and will cause errors when navigating between routes.`); } else { console.warn("[nuxt] `` needs to be passed a single root node in its default slot."); } } }); }); } return () => { if (!name || typeof name === "string" && !(name in layouts)) { if (import.meta.dev && import.meta.client && props.hasTransition) { vnode = context.slots.default?.(); return vnode; } return context.slots.default?.(); } if (import.meta.dev && import.meta.client && props.hasTransition) { vnode = h( LayoutLoader, { key: name, layoutProps: props.layoutProps, name }, context.slots ); return vnode; } return h( LayoutLoader, { key: name, layoutProps: props.layoutProps, name }, context.slots ); }; } });