fix(nuxt): ensure <NuxtLayout> fallback prop is typed (#30832)

This commit is contained in:
Maik Kowol 2025-02-01 14:45:12 +01:00 committed by GitHub
parent 34a731d00e
commit d9ba0d2249
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 17 deletions

View File

@ -1,4 +1,4 @@
import type { DefineComponent, MaybeRef, VNode } from 'vue' import type { DefineComponent, ExtractPublicPropTypes, MaybeRef, PropType, VNode } from 'vue'
import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue' import { Suspense, computed, defineComponent, h, inject, mergeProps, nextTick, onMounted, provide, ref, unref } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router' import type { RouteLocationNormalizedLoaded } from 'vue-router'
@ -30,19 +30,23 @@ const LayoutLoader = defineComponent({
}, },
}) })
// props are moved outside of defineComponent to later explicitly assert the prop types
// this avoids type loss/simplification resulting in things like MaybeRef<string | false>, keeping type hints for layout names
const nuxtLayoutProps = {
name: {
type: [String, Boolean, Object] as PropType<unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout']>,
default: null,
},
fallback: {
type: [String, Object] as PropType<unknown extends PageMeta['layout'] ? MaybeRef<string> : PageMeta['layout']>,
default: null,
},
}
export default defineComponent({ export default defineComponent({
name: 'NuxtLayout', name: 'NuxtLayout',
inheritAttrs: false, inheritAttrs: false,
props: { props: nuxtLayoutProps,
name: {
type: [String, Boolean, Object] as unknown as () => unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout'],
default: null,
},
fallback: {
type: [String, Object] as unknown as () => unknown extends PageMeta['layout'] ? MaybeRef<string> : PageMeta['layout'],
default: null,
},
},
setup (props, context) { setup (props, context) {
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
// Need to ensure (if we are not a child of `<NuxtPage>`) that we use synchronous route (not deferred) // Need to ensure (if we are not a child of `<NuxtPage>`) that we use synchronous route (not deferred)
@ -95,9 +99,7 @@ export default defineComponent({
}).default() }).default()
} }
}, },
}) as unknown as DefineComponent<{ }) as DefineComponent<ExtractPublicPropTypes<typeof nuxtLayoutProps>>
name?: (unknown extends PageMeta['layout'] ? MaybeRef<string | false> : PageMeta['layout']) | undefined
}>
const LayoutProvider = defineComponent({ const LayoutProvider = defineComponent({
name: 'NuxtLayoutProvider', name: 'NuxtLayoutProvider',

View File

@ -258,7 +258,7 @@ describe('typed router integration', () => {
}) })
describe('layouts', () => { describe('layouts', () => {
it('recognizes named layouts', () => { it('definePageMeta recognizes named layouts', () => {
definePageMeta({ layout: 'custom' }) definePageMeta({ layout: 'custom' })
definePageMeta({ layout: 'pascal-case' }) definePageMeta({ layout: 'pascal-case' })
definePageMeta({ layout: 'override' }) definePageMeta({ layout: 'override' })
@ -266,11 +266,14 @@ describe('layouts', () => {
definePageMeta({ layout: 'invalid-layout' }) definePageMeta({ layout: 'invalid-layout' })
}) })
it('allows typing layouts', () => { it('NuxtLayout recognizes named layouts', () => {
h(NuxtLayout, { name: 'custom' }) h(NuxtLayout, { name: 'custom' })
// @ts-expect-error Invalid layout // @ts-expect-error Invalid layout
h(NuxtLayout, { name: 'invalid-layout' }) h(NuxtLayout, { name: 'invalid-layout' })
h(NuxtLayout, { fallback: 'custom' })
// @ts-expect-error Invalid layout
h(NuxtLayout, { fallback: 'invalid-layout' })
}) })
}) })