Skip to content

Commit

Permalink
fix(layout): use suspense to delay render of layout items (#15229)
Browse files Browse the repository at this point in the history
Co-authored-by: John Leider <john.j.leider@gmail.com>
Co-authored-by: John Leider <john@vuetifyjs.com>
Co-authored-by: Kael <kaelwd@gmail.com>
  • Loading branch information
4 people authored Feb 14, 2024
1 parent 73cc1eb commit 727bc1a
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 66 deletions.
7 changes: 6 additions & 1 deletion packages/vuetify/src/components/VApp/VApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -41,7 +42,11 @@ export const VApp = genericComponent()({
]}
>
<div class="v-application__wrap">
{ slots.default?.() }
<Suspense>
<>
{ slots.default?.() }
</>
</Suspense>
</div>
</div>
))
Expand Down
10 changes: 4 additions & 6 deletions packages/vuetify/src/components/VAppBar/VAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,8 @@ export const VAppBar = genericComponent<VToolbarSlots>()({
: 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)
})
Expand All @@ -122,7 +120,7 @@ export const VAppBar = genericComponent<VToolbarSlots>()({
})

const { ssrBootStyles } = useSsrBoot()
const { layoutItemStyles } = useLayoutItem({
const { layoutItemStyles, layoutIsReady } = useLayoutItem({
id: props.name,
order: computed(() => parseInt(props.order, 10)),
position: toRef(props, 'location'),
Expand Down Expand Up @@ -162,7 +160,7 @@ export const VAppBar = genericComponent<VToolbarSlots>()({
)
})

return {}
return layoutIsReady
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -84,8 +85,8 @@ export const VBottomNavigation = genericComponent<new <T>(
(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'),
Expand Down Expand Up @@ -144,7 +145,7 @@ export const VBottomNavigation = genericComponent<new <T>(
)
})

return {}
return layoutIsReady
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => (
<VLayout>
<VBottomNavigation modelValue={ false }></VBottomNavigation>
</VLayout>
))

cy.get('.v-bottom-navigation').should('not.be.visible')
})
})
4 changes: 2 additions & 2 deletions packages/vuetify/src/components/VFooter/VFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -84,7 +84,7 @@ export const VFooter = genericComponent()({
/>
))

return {}
return props.app ? layoutIsReady : {}
},
})

Expand Down
7 changes: 6 additions & 1 deletion packages/vuetify/src/components/VLayout/VLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -33,7 +34,11 @@ export const VLayout = genericComponent()({
props.style,
]}
>
{ slots.default?.() }
<Suspense>
<>
{ slots.default?.() }
</>
</Suspense>
</div>
))

Expand Down
10 changes: 6 additions & 4 deletions packages/vuetify/src/components/VLayout/VLayoutItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'),
Expand All @@ -43,7 +43,7 @@ export const VLayoutItem = genericComponent()({
absolute: toRef(props, 'absolute'),
})

return () => (
useRender(() => (
<div
class={[
'v-layout-item',
Expand All @@ -56,7 +56,9 @@ export const VLayoutItem = genericComponent()({
>
{ slots.default?.() }
</div>
)
))

return layoutIsReady
},
})

Expand Down
4 changes: 2 additions & 2 deletions packages/vuetify/src/components/VMain/VMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const VMain = genericComponent()({
props: makeVMainProps(),

setup (props, { slots }) {
const { mainStyles } = useLayout()
const { mainStyles, layoutIsReady } = useLayout()
const { ssrBootStyles } = useSsrBoot()

useRender(() => (
Expand All @@ -50,7 +50,7 @@ export const VMain = genericComponent()({
</props.tag>
))

return {}
return layoutIsReady
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -145,11 +145,9 @@ export const VNavigationDrawer = genericComponent<VNavigationDrawerSlots>()({
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,
Expand All @@ -166,8 +164,7 @@ export const VNavigationDrawer = genericComponent<VNavigationDrawerSlots>()({

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,
Expand Down Expand Up @@ -287,9 +284,7 @@ export const VNavigationDrawer = genericComponent<VNavigationDrawerSlots>()({
)
})

return {
isStuck,
}
return layoutIsReady.then(() => ({ isStuck }))
},
})

Expand Down
51 changes: 15 additions & 36 deletions packages/vuetify/src/composables/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -59,6 +59,7 @@ interface LayoutProvide {
items: Ref<LayoutItem[]>
layoutRect: Ref<DOMRectReadOnly | undefined>
rootZIndex: Ref<number>
layoutIsReady: Promise<void>
}

export const VuetifyLayoutKey: InjectionKey<LayoutProvide> = Symbol.for('vuetify:layout')
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -122,6 +126,8 @@ export function useLayoutItem (options: {
onDeactivated(() => isKeptAlive.value = true)
onActivated(() => isKeptAlive.value = false)

const layoutIsReady = nextTick()

const {
layoutItemStyles,
layoutItemScrimStyles,
Expand All @@ -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 = (
Expand Down Expand Up @@ -177,28 +183,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean
const disabledTransitions = reactive(new Map<string, Ref<boolean>>())
const { resizeRef, contentRect: layoutRect } = useResizeObserver()

const computedOverlaps = computed(() => {
const map = new Map<string, { position: Position, amount: number }>()
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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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: (
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -342,6 +319,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean
items,
layoutRect,
rootZIndex,
layoutIsReady,
})

const layoutClasses = computed(() => [
Expand All @@ -361,6 +339,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean
getLayoutItem,
items,
layoutRect,
layoutIsReady,
layoutRef: resizeRef,
}
}
Loading

0 comments on commit 727bc1a

Please sign in to comment.