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)
+}
]