diff --git a/docs/content/3.docs/2.directory-structure/6.layouts.md b/docs/content/3.docs/2.directory-structure/6.layouts.md index 65112b2b13..591d641d27 100644 --- a/docs/content/3.docs/2.directory-structure/6.layouts.md +++ b/docs/content/3.docs/2.directory-structure/6.layouts.md @@ -79,12 +79,10 @@ You can also use a ref or computed property for your layout. ``` diff --git a/packages/nuxt3/src/pages/module.ts b/packages/nuxt3/src/pages/module.ts index 85300296a6..7e51754713 100644 --- a/packages/nuxt3/src/pages/module.ts +++ b/packages/nuxt3/src/pages/module.ts @@ -112,8 +112,27 @@ export default defineNuxtModule({ } }) + addTemplate({ + filename: 'layouts.d.ts', + write: true, + getContents: async () => { + const composablesFile = resolve(runtimeDir, 'composables') + const layouts = await resolveLayouts(nuxt) + return [ + 'import { ComputedRef, Ref } from \'vue\'', + `export type LayoutKey = ${layouts.map(layout => `"${layout.name}"`).join(' | ') || 'string'}`, + `declare module '${composablesFile}' {`, + ' interface PageMeta {', + ' layout?: false | LayoutKey | Ref | ComputedRef', + ' }', + '}' + ].join('\n') + } + }) + nuxt.hook('prepare:types', ({ references }) => { references.push({ path: resolve(nuxt.options.buildDir, 'middleware.d.ts') }) + references.push({ path: resolve(nuxt.options.buildDir, 'layouts.d.ts') }) }) // Add layouts template diff --git a/packages/nuxt3/src/pages/runtime/composables.ts b/packages/nuxt3/src/pages/runtime/composables.ts index 68a68eb1bc..2072cf32e0 100644 --- a/packages/nuxt3/src/pages/runtime/composables.ts +++ b/packages/nuxt3/src/pages/runtime/composables.ts @@ -1,4 +1,4 @@ -import { ComputedRef, KeepAliveProps, Ref, TransitionProps } from 'vue' +import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue' import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router' import { useNuxtApp } from '#app' @@ -14,13 +14,12 @@ export interface PageMeta { [key: string]: any pageTransition?: false | TransitionProps layoutTransition?: false | TransitionProps - layout?: false | string | Ref | ComputedRef key?: string | ((route: RouteLocationNormalizedLoaded) => string) keepalive?: false | KeepAliveProps } declare module 'vue-router' { - interface RouteMeta extends PageMeta {} + interface RouteMeta extends UnwrapRef {} } const warnRuntimeUsage = (method: string) => diff --git a/packages/nuxt3/src/pages/runtime/page.ts b/packages/nuxt3/src/pages/runtime/page.ts index b2d2f1b980..4dad687375 100644 --- a/packages/nuxt3/src/pages/runtime/page.ts +++ b/packages/nuxt3/src/pages/runtime/page.ts @@ -25,26 +25,34 @@ export default defineComponent({ const hasLayout = props.layout ?? route.meta.layout ?? 'default' in layouts return h(RouterView, {}, { - default: ({ Component }: RouterViewSlotProps) => Component && wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? defaultLayoutTransition), { - default: () => wrapIf(NuxtLayout, hasLayout && { layout: props.layout ?? route.meta.layout }, { - default: () => wrapIf(Transition, route.meta.pageTransition ?? defaultPageTransition, { - default: () => wrapIf(KeepAlive, process.client && route.meta.keepalive, h(Suspense, { + default: ({ Component }: RouterViewSlotProps) => Component && + wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? defaultLayoutTransition), + wrapIf(NuxtLayout, hasLayout && { name: props.layout ?? route.meta.layout }, + wrapIf(Transition, route.meta.pageTransition ?? defaultPageTransition, + wrapInKeepAlive(route.meta.keepalive, h(Suspense, { onPending: () => nuxtApp.callHook('page:start', Component), onResolve: () => nuxtApp.callHook('page:finish', Component) - }, { default: () => h(Component) })) - }) - }) - }) + }, { default: () => h(Component) }) + ) + ) + )).default() }) } } }) -const wrapIf = (component: Component, props: any, slotsOrChildren: any) => { - if (props) { - return h(component, props === true ? {} : props, slotsOrChildren) +const Fragment = { + setup (props, { slots }) { + return () => slots.default() } - return slotsOrChildren.default?.() || slotsOrChildren +} + +const wrapIf = (component: Component, props: any, slots: any) => { + return { default: () => props ? h(component, props === true ? {} : props, slots) : h(Fragment, {}, slots) } +} + +const wrapInKeepAlive = (props: any, children: any) => { + return { default: () => process.client && props ? h(KeepAlive, props === true ? {} : props, children) : children } } const defaultLayoutTransition = { name: 'layout', mode: 'out-in' } diff --git a/packages/nuxt3/src/pages/runtime/router.ts b/packages/nuxt3/src/pages/runtime/router.ts index a8df95a41a..e0f0a2906e 100644 --- a/packages/nuxt3/src/pages/runtime/router.ts +++ b/packages/nuxt3/src/pages/runtime/router.ts @@ -66,6 +66,7 @@ export default defineNuxtPlugin((nuxtApp) => { } router.beforeEach(async (to, from) => { + to.meta = reactive(to.meta) nuxtApp._processingMiddleware = true type MiddlewareDef = string | NavigationGuard