quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
286 lines (238 loc) • 7.36 kB
JavaScript
import {
h,
ref,
reactive,
computed,
watch,
provide,
onUnmounted,
getCurrentInstance
} from 'vue'
import { isRuntimeSsrPreHydration } from '../../plugins/platform/Platform.js'
import QScrollObserver from '../scroll-observer/QScrollObserver.js'
import QResizeObserver from '../resize-observer/QResizeObserver.js'
import { createComponent } from '../../utils/private.create/create.js'
import { getScrollbarWidth } from '../../utils/scroll/scroll.js'
import { hMergeSlot } from '../../utils/private.render/render.js'
import { layoutKey } from '../../utils/private.symbols/symbols.js'
export default createComponent({
name: 'QLayout',
props: {
container: Boolean,
view: {
type: String,
default: 'hhh lpr fff',
validator: v => /^(h|l)h(h|r) lpr (f|l)f(f|r)$/.test(v.toLowerCase())
},
onScroll: Function,
onScrollHeight: Function,
onResize: Function
},
setup(props, { slots, emit }) {
const {
proxy: { $q }
} = getCurrentInstance()
const rootRef = ref(null)
// page related
const height = ref($q.screen.height)
const width = ref(props.container === true ? 0 : $q.screen.width)
const scroll = ref({ position: 0, direction: 'down', inflectionPoint: 0 })
// container only prop
const containerHeight = ref(0)
const scrollbarWidth = ref(
isRuntimeSsrPreHydration.value === true ? 0 : getScrollbarWidth()
)
const classes = computed(
() =>
'q-layout q-layout--' +
(props.container === true ? 'containerized' : 'standard')
)
const style = computed(() =>
props.container === false ? { minHeight: $q.screen.height + 'px' } : null
)
// used by container only
const targetStyle = computed(() =>
scrollbarWidth.value !== 0
? {
[$q.lang.rtl === true ? 'left' : 'right']:
`${scrollbarWidth.value}px`
}
: null
)
const targetChildStyle = computed(() =>
scrollbarWidth.value !== 0
? {
[$q.lang.rtl === true ? 'right' : 'left']: 0,
[$q.lang.rtl === true ? 'left' : 'right']:
`-${scrollbarWidth.value}px`,
width: `calc(100% + ${scrollbarWidth.value}px)`
}
: null
)
function onPageScroll(data) {
if (props.container === true || document.qScrollPrevented !== true) {
const info = {
position: data.position.top,
direction: data.direction,
directionChanged: data.directionChanged,
inflectionPoint: data.inflectionPoint.top,
delta: data.delta.top
}
scroll.value = info
if (props.onScroll !== void 0) emit('scroll', info)
}
}
function onPageResize(data) {
const { height: newHeight, width: newWidth } = data
let resized = false
if (height.value !== newHeight) {
resized = true
height.value = newHeight
if (props.onScrollHeight !== void 0) emit('scrollHeight', newHeight)
updateScrollbarWidth()
}
if (width.value !== newWidth) {
resized = true
width.value = newWidth
}
if (resized === true && props.onResize !== void 0) {
emit('resize', data)
}
}
function onContainerResize({ height: newHeight }) {
if (containerHeight.value !== newHeight) {
containerHeight.value = newHeight
updateScrollbarWidth()
}
}
function updateScrollbarWidth() {
if (props.container === true) {
const newWidth =
height.value > containerHeight.value ? getScrollbarWidth() : 0
if (scrollbarWidth.value !== newWidth) {
scrollbarWidth.value = newWidth
}
}
}
let animateTimer = null
const $layout = {
instances: {},
view: computed(() => props.view),
isContainer: computed(() => props.container),
rootRef,
height,
containerHeight,
scrollbarWidth,
totalWidth: computed(() => width.value + scrollbarWidth.value),
rows: computed(() => {
const rows = props.view.toLowerCase().split(' ')
return {
top: rows[0].split(''),
middle: rows[1].split(''),
bottom: rows[2].split('')
}
}),
header: reactive({ size: 0, offset: 0, space: false }),
right: reactive({ size: 300, offset: 0, space: false }),
footer: reactive({ size: 0, offset: 0, space: false }),
left: reactive({ size: 300, offset: 0, space: false }),
scroll,
animate() {
if (animateTimer !== null) {
clearTimeout(animateTimer)
} else {
document.body.classList.add('q-body--layout-animate')
}
animateTimer = setTimeout(() => {
animateTimer = null
document.body.classList.remove('q-body--layout-animate')
}, 155)
},
update(part, prop, val) {
$layout[part][prop] = val
}
}
provide(layoutKey, $layout)
// prevent scrollbar flicker while resizing window height
// if no page scrollbar is already present
if (__QUASAR_SSR_SERVER__ !== true && getScrollbarWidth() > 0) {
let timer = null
const el = document.body
function restoreScrollbar() {
timer = null
el.classList.remove('hide-scrollbar')
}
function hideScrollbar() {
if (timer === null) {
// if it has no scrollbar then there's nothing to do
if (el.scrollHeight > $q.screen.height) return
el.classList.add('hide-scrollbar')
} else {
clearTimeout(timer)
}
timer = setTimeout(restoreScrollbar, 300)
}
function updateScrollEvent(action) {
if (timer !== null && action === 'remove') {
clearTimeout(timer)
restoreScrollbar()
}
window[`${action}EventListener`]('resize', hideScrollbar)
}
watch(
() => (props.container !== true ? 'add' : 'remove'),
updateScrollEvent
)
if (props.container !== true) updateScrollEvent('add')
onUnmounted(() => {
updateScrollEvent('remove')
})
}
return () => {
const content = hMergeSlot(slots.default, [
h(QScrollObserver, { onScroll: onPageScroll }),
h(QResizeObserver, { onResize: onPageResize })
])
const layout = h(
'div',
{
class: classes.value,
style: style.value,
ref: props.container === true ? void 0 : rootRef,
tabindex: -1
},
content
)
if (props.container === true) {
return h(
'div',
{
class: 'q-layout-container overflow-hidden',
ref: rootRef
},
[
h(QResizeObserver, { onResize: onContainerResize }),
h(
'div',
{
class: 'absolute-full',
style: targetStyle.value
},
[
h(
'div',
{
class: 'scroll',
style: targetChildStyle.value
},
[layout]
)
]
)
]
)
}
return layout
}
}
})