diff --git a/packages/vuetify/src/components/VApp/VApp.tsx b/packages/vuetify/src/components/VApp/VApp.tsx index fdbb9870bc9..063152fc1f7 100644 --- a/packages/vuetify/src/components/VApp/VApp.tsx +++ b/packages/vuetify/src/components/VApp/VApp.tsx @@ -8,6 +8,7 @@ import { useRtl } from '@/composables/locale' import { makeThemeProps, provideTheme } from '@/composables/theme' // Utilities +import { Suspense } from 'vue' import { genericComponent, propsFactory, useRender } from '@/util' export const makeVAppProps = propsFactory({ @@ -41,7 +42,11 @@ export const VApp = genericComponent()({ ]} >
- { slots.default?.() } + + <> + { slots.default?.() } + +
)) diff --git a/packages/vuetify/src/components/VAppBar/VAppBar.tsx b/packages/vuetify/src/components/VAppBar/VAppBar.tsx index ab1fcd0842b..1fe67bdfa5e 100644 --- a/packages/vuetify/src/components/VAppBar/VAppBar.tsx +++ b/packages/vuetify/src/components/VAppBar/VAppBar.tsx @@ -99,10 +99,8 @@ export const VAppBar = genericComponent()({ : undefined )) const height = computed(() => { - if (scrollBehavior.value.hide && scrollBehavior.value.inverted) return 0 - - const height = vToolbarRef.value?.contentHeight ?? 0 - const extensionHeight = vToolbarRef.value?.extensionHeight ?? 0 + const height = Number(vToolbarRef.value?.contentHeight ?? props.height) + const extensionHeight = Number(vToolbarRef.value?.extensionHeight ?? 0) return (height + extensionHeight) }) @@ -122,7 +120,7 @@ export const VAppBar = genericComponent()({ }) const { ssrBootStyles } = useSsrBoot() - const { layoutItemStyles } = useLayoutItem({ + const { layoutItemStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: toRef(props, 'location'), @@ -162,7 +160,7 @@ export const VAppBar = genericComponent()({ ) }) - return {} + return layoutIsReady }, }) diff --git a/packages/vuetify/src/components/VBottomNavigation/VBottomNavigation.tsx b/packages/vuetify/src/components/VBottomNavigation/VBottomNavigation.tsx index d666b124eaf..d64d0e30f4b 100644 --- a/packages/vuetify/src/components/VBottomNavigation/VBottomNavigation.tsx +++ b/packages/vuetify/src/components/VBottomNavigation/VBottomNavigation.tsx @@ -13,6 +13,7 @@ import { makeDensityProps, useDensity } from '@/composables/density' import { makeElevationProps, useElevation } from '@/composables/elevation' import { makeGroupProps, useGroup } from '@/composables/group' import { makeLayoutItemProps, useLayoutItem } from '@/composables/layout' +import { useProxiedModel } from '@/composables/proxiedModel' import { makeRoundedProps, useRounded } from '@/composables/rounded' import { useSsrBoot } from '@/composables/ssrBoot' import { makeTagProps } from '@/composables/tag' @@ -84,8 +85,8 @@ export const VBottomNavigation = genericComponent( (props.density === 'comfortable' ? 8 : 0) - (props.density === 'compact' ? 16 : 0) )) - const isActive = toRef(props, 'active') - const { layoutItemStyles } = useLayoutItem({ + const isActive = useProxiedModel(props, 'modelValue', props.modelValue) + const { layoutItemStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: computed(() => 'bottom'), @@ -144,7 +145,7 @@ export const VBottomNavigation = genericComponent( ) }) - return {} + return layoutIsReady }, }) diff --git a/packages/vuetify/src/components/VBottomNavigation/__tests__/VBottomNavigation.spec.cy.tsx b/packages/vuetify/src/components/VBottomNavigation/__tests__/VBottomNavigation.spec.cy.tsx index 1243c61139f..9821f1aa495 100644 --- a/packages/vuetify/src/components/VBottomNavigation/__tests__/VBottomNavigation.spec.cy.tsx +++ b/packages/vuetify/src/components/VBottomNavigation/__tests__/VBottomNavigation.spec.cy.tsx @@ -28,4 +28,14 @@ describe('VBottomNavigation', () => { .setProps({ density: 'compact' }) .get('.v-bottom-navigation').should('have.css', 'height', '40px') }) + + it('should not be visible if modelValue is false', () => { + cy.mount(() => ( + + + + )) + + cy.get('.v-bottom-navigation').should('not.be.visible') + }) }) diff --git a/packages/vuetify/src/components/VFooter/VFooter.tsx b/packages/vuetify/src/components/VFooter/VFooter.tsx index cbff729e866..3fa2347e7d9 100644 --- a/packages/vuetify/src/components/VFooter/VFooter.tsx +++ b/packages/vuetify/src/components/VFooter/VFooter.tsx @@ -51,7 +51,7 @@ export const VFooter = genericComponent()({ autoHeight.value = entries[0].target.clientHeight }) const height = computed(() => props.height === 'auto' ? autoHeight.value : parseInt(props.height, 10)) - const { layoutItemStyles } = useLayoutItem({ + const { layoutItemStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: computed(() => 'bottom'), @@ -84,7 +84,7 @@ export const VFooter = genericComponent()({ /> )) - return {} + return props.app ? layoutIsReady : {} }, }) diff --git a/packages/vuetify/src/components/VLayout/VLayout.tsx b/packages/vuetify/src/components/VLayout/VLayout.tsx index fa6bc1558dd..e9467d97962 100644 --- a/packages/vuetify/src/components/VLayout/VLayout.tsx +++ b/packages/vuetify/src/components/VLayout/VLayout.tsx @@ -6,6 +6,7 @@ import { makeComponentProps } from '@/composables/component' import { createLayout, makeLayoutProps } from '@/composables/layout' // Utilities +import { Suspense } from 'vue' import { genericComponent, propsFactory, useRender } from '@/util' export const makeVLayoutProps = propsFactory({ @@ -33,7 +34,11 @@ export const VLayout = genericComponent()({ props.style, ]} > - { slots.default?.() } + + <> + { slots.default?.() } + + )) diff --git a/packages/vuetify/src/components/VLayout/VLayoutItem.tsx b/packages/vuetify/src/components/VLayout/VLayoutItem.tsx index b42c9379dca..f7f9af525b0 100644 --- a/packages/vuetify/src/components/VLayout/VLayoutItem.tsx +++ b/packages/vuetify/src/components/VLayout/VLayoutItem.tsx @@ -7,7 +7,7 @@ import { makeLayoutItemProps, useLayoutItem } from '@/composables/layout' // Utilities import { computed, toRef } from 'vue' -import { genericComponent, propsFactory } from '@/util' +import { genericComponent, propsFactory, useRender } from '@/util' // Types import type { PropType } from 'vue' @@ -33,7 +33,7 @@ export const VLayoutItem = genericComponent()({ props: makeVLayoutItemProps(), setup (props, { slots }) { - const { layoutItemStyles } = useLayoutItem({ + const { layoutItemStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: toRef(props, 'position'), @@ -43,7 +43,7 @@ export const VLayoutItem = genericComponent()({ absolute: toRef(props, 'absolute'), }) - return () => ( + useRender(() => (
{ slots.default?.() }
- ) + )) + + return layoutIsReady }, }) diff --git a/packages/vuetify/src/components/VMain/VMain.tsx b/packages/vuetify/src/components/VMain/VMain.tsx index cf3fc1aa799..d505e719f9a 100644 --- a/packages/vuetify/src/components/VMain/VMain.tsx +++ b/packages/vuetify/src/components/VMain/VMain.tsx @@ -23,7 +23,7 @@ export const VMain = genericComponent()({ props: makeVMainProps(), setup (props, { slots }) { - const { mainStyles } = useLayout() + const { mainStyles, layoutIsReady } = useLayout() const { ssrBootStyles } = useSsrBoot() useRender(() => ( @@ -50,7 +50,7 @@ export const VMain = genericComponent()({ )) - return {} + return layoutIsReady }, }) diff --git a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx index 9cbde45a8b0..a6004f2f37e 100644 --- a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx +++ b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx @@ -22,7 +22,7 @@ import { makeThemeProps, provideTheme } from '@/composables/theme' import { useToggleScope } from '@/composables/toggleScope' // Utilities -import { computed, nextTick, onBeforeMount, ref, shallowRef, toRef, Transition, watch } from 'vue' +import { computed, nextTick, ref, shallowRef, toRef, Transition, watch } from 'vue' import { genericComponent, propsFactory, toPhysical, useRender } from '@/util' // Types @@ -145,11 +145,9 @@ export const VNavigationDrawer = genericComponent()({ if (val) isActive.value = true }) - onBeforeMount(() => { - if (props.modelValue != null || isTemporary.value) return - + if (props.modelValue == null && !isTemporary.value) { isActive.value = props.permanent || !mobile.value - }) + } const { isDragging, dragProgress, dragStyles } = useTouch({ isActive, @@ -166,8 +164,7 @@ export const VNavigationDrawer = genericComponent()({ return isDragging.value ? size * dragProgress.value : size }) - - const { layoutItemStyles, layoutItemScrimStyles } = useLayoutItem({ + const { layoutItemStyles, layoutItemScrimStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), position: location, @@ -287,9 +284,7 @@ export const VNavigationDrawer = genericComponent()({ ) }) - return { - isStuck, - } + return layoutIsReady.then(() => ({ isStuck })) }, }) diff --git a/packages/vuetify/src/composables/layout.ts b/packages/vuetify/src/composables/layout.ts index 1ca78909390..f851fbb036e 100644 --- a/packages/vuetify/src/composables/layout.ts +++ b/packages/vuetify/src/composables/layout.ts @@ -5,16 +5,16 @@ import { useResizeObserver } from '@/composables/resizeObserver' import { computed, inject, + nextTick, onActivated, onBeforeUnmount, onDeactivated, - onMounted, provide, reactive, ref, shallowRef, } from 'vue' -import { convertToUnit, findChildrenWithProvide, getCurrentInstance, getUid, propsFactory } from '@/util' +import { convertToUnit, eagerComputed, findChildrenWithProvide, getCurrentInstance, getUid, propsFactory } from '@/util' // Types import type { ComponentInternalInstance, CSSProperties, InjectionKey, Prop, Ref } from 'vue' @@ -59,6 +59,7 @@ interface LayoutProvide { items: Ref layoutRect: Ref rootZIndex: Ref + layoutIsReady: Promise } export const VuetifyLayoutKey: InjectionKey = Symbol.for('vuetify:layout') @@ -91,7 +92,10 @@ export function useLayout () { if (!layout) throw new Error('[Vuetify] Could not find injected layout') + const layoutIsReady = nextTick() + return { + layoutIsReady, getLayoutItem: layout.getLayoutItem, mainRect: layout.mainRect, mainStyles: layout.mainStyles, @@ -122,6 +126,8 @@ export function useLayoutItem (options: { onDeactivated(() => isKeptAlive.value = true) onActivated(() => isKeptAlive.value = false) + const layoutIsReady = nextTick() + const { layoutItemStyles, layoutItemScrimStyles, @@ -133,7 +139,7 @@ export function useLayoutItem (options: { onBeforeUnmount(() => layout.unregister(id)) - return { layoutItemStyles, layoutRect: layout.layoutRect, layoutItemScrimStyles } + return { layoutItemStyles, layoutRect: layout.layoutRect, layoutItemScrimStyles, layoutIsReady } } const generateLayers = ( @@ -177,28 +183,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean const disabledTransitions = reactive(new Map>()) const { resizeRef, contentRect: layoutRect } = useResizeObserver() - const computedOverlaps = computed(() => { - const map = new Map() - const overlaps = props.overlaps ?? [] - for (const overlap of overlaps.filter(item => item.includes(':'))) { - const [top, bottom] = overlap.split(':') - if (!registered.value.includes(top) || !registered.value.includes(bottom)) continue - - const topPosition = positions.get(top) - const bottomPosition = positions.get(bottom) - const topAmount = layoutSizes.get(top) - const bottomAmount = layoutSizes.get(bottom) - - if (!topPosition || !bottomPosition || !topAmount || !bottomAmount) continue - - map.set(bottom, { position: topPosition.value, amount: parseInt(topAmount.value, 10) }) - map.set(top, { position: bottomPosition.value, amount: -parseInt(bottomAmount.value, 10) }) - } - - return map - }) - - const layers = computed(() => { + const layers = eagerComputed(() => { const uniquePriorities = [...new Set([...priorities.values()].map(p => p.value))].sort((a, b) => a - b) const layout = [] for (const p of uniquePriorities) { @@ -226,7 +211,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean } }) - const items = computed(() => { + const items = eagerComputed(() => { return layers.value.slice(1).map(({ id }, index) => { const { layer } = layers.value[index] const size = layoutSizes.get(id) @@ -247,10 +232,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean const rootVm = getCurrentInstance('createLayout') - const isMounted = shallowRef(false) - onMounted(() => { - isMounted.value = true - }) + const layoutIsReady = nextTick() provide(VuetifyLayoutKey, { register: ( @@ -294,17 +276,12 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean ...(transitionsEnabled.value ? undefined : { transition: 'none' }), } as const - if (!isMounted.value) return styles + if (index.value < 0) throw new Error(`Layout item "${id}" is missing`) const item = items.value[index.value] if (!item) throw new Error(`[Vuetify] Could not find layout item "${id}"`) - const overlap = computedOverlaps.value.get(id) - if (overlap) { - item[overlap.position] += overlap.amount - } - return { ...styles, height: @@ -342,6 +319,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean items, layoutRect, rootZIndex, + layoutIsReady, }) const layoutClasses = computed(() => [ @@ -361,6 +339,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean getLayoutItem, items, layoutRect, + layoutIsReady, layoutRef: resizeRef, } } diff --git a/packages/vuetify/src/util/helpers.ts b/packages/vuetify/src/util/helpers.ts index 3a777fd3a72..cd80daec6e6 100644 --- a/packages/vuetify/src/util/helpers.ts +++ b/packages/vuetify/src/util/helpers.ts @@ -1,5 +1,5 @@ // Utilities -import { capitalize, Comment, computed, Fragment, isVNode, reactive, toRefs, unref, watchEffect } from 'vue' +import { capitalize, Comment, computed, Fragment, isVNode, reactive, readonly, shallowRef, toRefs, unref, watchEffect } from 'vue' import { IN_BROWSER } from '@/util/globals' // Types @@ -14,6 +14,7 @@ import type { VNode, VNodeArrayChildren, VNodeChild, + WatchOptions, } from 'vue' export function getNestedValue (obj: any, path: (string | number)[], fallback?: any): any { @@ -708,3 +709,16 @@ export function defer (timeout: number, cb: () => void) { return () => window.clearTimeout(timeoutId) } + +export function eagerComputed (fn: () => T, options?: WatchOptions): Readonly> { + const result = shallowRef() + + watchEffect(() => { + result.value = fn() + }, { + flush: 'sync', + ...options, + }) + + return readonly(result) +}