Skip to content

Commit

Permalink
fix(layout): use suspense to delay render of layout items
Browse files Browse the repository at this point in the history
closes #15202
  • Loading branch information
nekosaur committed Jun 29, 2022
1 parent 32c5b6b commit e501d14
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 55 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 @@ -7,6 +7,7 @@ import { makeThemeProps, provideTheme } from '@/composables/theme'
import { useRtl } from '@/composables/rtl'

// Utilities
import { Suspense } from 'vue'
import { defineComponent, useRender } from '@/util'

export const VApp = defineComponent({
Expand Down Expand Up @@ -34,7 +35,11 @@ export const VApp = defineComponent({
style={ layoutStyles.value }
>
<div class="v-application__wrap">
{ slots.default?.() }
<Suspense>
<>
{ slots.default?.() }
</>
</Suspense>
</div>
</div>
))
Expand Down
6 changes: 3 additions & 3 deletions packages/vuetify/src/components/VAppBar/VAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export const VAppBar = defineComponent({
const vToolbarRef = ref()
const isActive = useProxiedModel(props, 'modelValue')
const height = computed(() => {
const height: number = vToolbarRef.value?.contentHeight ?? 0
const height: number = vToolbarRef.value?.contentHeight ?? props.height
const extensionHeight: number = vToolbarRef.value?.extensionHeight ?? 0

return (height + extensionHeight)
})
const { layoutItemStyles } = useLayoutItem({
const { layoutItemStyles, layoutIsReady } = useLayoutItem({
id: props.name,
order: computed(() => parseInt(props.order, 10)),
position: toRef(props, 'location'),
Expand Down Expand Up @@ -90,7 +90,7 @@ export const VAppBar = defineComponent({
)
})

return {}
return layoutIsReady
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const VBottomNavigation = defineComponent({
(props.density === 'compact' ? 16 : 0)
))
const isActive = useProxiedModel(props, 'modelValue', props.modelValue)
const { layoutItemStyles } = useLayoutItem({
const { layoutItemStyles, layoutIsReady } = useLayoutItem({
id: props.name,
order: computed(() => parseInt(props.order, 10)),
position: computed(() => 'bottom'),
Expand Down Expand Up @@ -123,7 +123,7 @@ export const VBottomNavigation = defineComponent({
)
})

return {}
return layoutIsReady
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,14 @@ describe('VBottomNavigation', () => {

cy.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 @@ -47,7 +47,7 @@ export const VFooter = defineComponent({
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 @@ -76,6 +76,6 @@ export const VFooter = defineComponent({
/>
))

return {}
return props.app ? layoutIsReady : {}
},
})
13 changes: 9 additions & 4 deletions packages/vuetify/src/components/VLayout/VLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Styles
import './VLayout.sass'

// Utilities
import { defineComponent, useRender } from '@/util'

// Composables
import { createLayout, makeLayoutProps } from '@/composables/layout'

// Utilities
import { Suspense } from 'vue'
import { defineComponent, useRender } from '@/util'

export const VLayout = defineComponent({
name: 'VLayout',

Expand All @@ -17,7 +18,11 @@ export const VLayout = defineComponent({

useRender(() => (
<div ref={ layoutRef } class={ layoutClasses.value } style={ layoutStyles.value }>
{ slots.default?.() }
<Suspense>
<>
{ slots.default?.() }
</>
</Suspense>
</div>
))

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 @@ -15,7 +15,7 @@ export const VMain = defineComponent({
props: makeTagProps({ tag: 'main' }),

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

useRender(() => (
Expand All @@ -32,6 +32,6 @@ export const VMain = defineComponent({
</props.tag>
))

return {}
return layoutIsReady
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useSsrBoot } from '@/composables/ssrBoot'
import { useTouch } from './touch'

// Utilities
import { computed, onBeforeMount, ref, toRef, Transition, watch } from 'vue'
import { computed, ref, toRef, Transition, watch } from 'vue'
import { convertToUnit, defineComponent, useRender } from '@/util'

// Types
Expand Down Expand Up @@ -96,11 +96,9 @@ export const VNavigationDrawer = defineComponent({
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 rootEl = ref<HTMLElement>()

Expand All @@ -119,7 +117,7 @@ export const VNavigationDrawer = defineComponent({

return isDragging.value ? size * dragProgress.value : size
})
const { layoutItemStyles, layoutRect, layoutItemScrimStyles } = useLayoutItem({
const { layoutItemStyles, layoutRect, layoutItemScrimStyles, layoutIsReady } = useLayoutItem({
id: props.name,
order: computed(() => parseInt(props.order, 10)),
position: toRef(props, 'location'),
Expand Down Expand Up @@ -219,7 +217,7 @@ export const VNavigationDrawer = defineComponent({
)
})

return {}
return layoutIsReady
},
})

Expand Down
42 changes: 8 additions & 34 deletions packages/vuetify/src/composables/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { useResizeObserver } from '@/composables/resizeObserver'
import {
computed,
inject,
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted,
provide,
reactive,
ref,
Expand Down Expand Up @@ -53,6 +53,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 @@ -112,6 +113,8 @@ export function useLayoutItem (options: {
onDeactivated(() => isKeptAlive.value = true)
onActivated(() => isKeptAlive.value = false)

const layoutIsReady = nextTick()

const {
layoutItemStyles,
layoutItemScrimStyles,
Expand All @@ -123,7 +126,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 @@ -167,27 +170,6 @@ 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 uniquePriorities = [...new Set([...priorities.values()].map(p => p.value))].sort((a, b) => a - b)
const layout = []
Expand Down Expand Up @@ -234,10 +216,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean

const rootVm = getCurrentInstance('createLayout')

const isMounted = ref(false)
onMounted(() => {
isMounted.value = true
})
const layoutIsReady = nextTick()

provide(VuetifyLayoutKey, {
register: (
Expand Down Expand Up @@ -281,19 +260,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(`Could not find layout item "${id}`)

const overlap = computedOverlaps.value.get(id)
if (overlap) {
item[overlap.position] += overlap.amount
}

return {
...styles,
height: isHorizontal ? `calc(100% - ${item.top}px - ${item.bottom}px)` : elementSize.value ? `${elementSize.value}px` : undefined,
Expand Down Expand Up @@ -325,6 +297,7 @@ export function createLayout (props: { overlaps?: string[], fullHeight?: boolean
items,
layoutRect,
rootZIndex,
layoutIsReady,
})

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

0 comments on commit e501d14

Please sign in to comment.