v11
This commit is contained in:
@@ -1,98 +1,125 @@
|
||||
<template>
|
||||
<q-dialog
|
||||
v-model="modelValue"
|
||||
maximized
|
||||
transition-show="slide-up"
|
||||
transition-hide="slide-down"
|
||||
position="bottom"
|
||||
>
|
||||
>
|
||||
<q-card
|
||||
class="fix-card-width flex column no-scroll no-wrap q-px-none"
|
||||
ref="cardRef"
|
||||
class="fix-card-width column no-scroll no-wrap q-px-none overflow-hidden relative-position"
|
||||
style="
|
||||
border-top-left-radius: var(--top-radius) !important;
|
||||
border-top-right-radius: var(--top-radius) !important;
|
||||
max-height: calc(100vh - 48px);
|
||||
max-height: calc(100vh - 48px - var(--tg-content-safe-area-inset-top) - var(--tg-safe-area-inset-top));
|
||||
"
|
||||
>
|
||||
<div
|
||||
ref="cardHeaderRef"
|
||||
class="flex items-center no-wrap justify-between w100 q-my-none q-pa-md"
|
||||
>
|
||||
<div class="text-h6 q-mx-xs ellipsis">{{ $t(title) }}</div>
|
||||
<div class="flex items-center justify-between no-wrap">
|
||||
<slot name="btnSlot">
|
||||
<q-btn
|
||||
icon="mdi-close"
|
||||
@click="modelValue = false"
|
||||
flat round
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="cardBodyRef"
|
||||
class="q-px-none q-ma-none"
|
||||
>
|
||||
<pn-shadow-scroll
|
||||
:hideShadows="false"
|
||||
:height="bodyHeight"
|
||||
>
|
||||
<div ref="cardBodyInnerRef" class="q-pa-none q-ma-none">
|
||||
<q-resize-observer @resize="updateDimensions" />
|
||||
<slot/>
|
||||
</div>
|
||||
</pn-shadow-scroll>
|
||||
</div>
|
||||
<div
|
||||
style="overflow: hidden"
|
||||
:style="{ height: footerHeight + 'px'}"
|
||||
>
|
||||
<div ref="cardFooterInnerRef" class="q-pa-md">
|
||||
<q-resize-observer @resize="updateDimensions" />
|
||||
<slot name="footer"/>
|
||||
<div ref="cardHeaderRef" class="flex items-center no-wrap justify-between w100 q-my-none q-pa-md">
|
||||
<q-resize-observer @resize="onHeaderResize" />
|
||||
<div class="flex column q-mx-xs">
|
||||
<span class="text-h6" style="line-height: 1.5rem;">
|
||||
{{ $t(title) }}
|
||||
</span>
|
||||
<span v-if="caption" class="text-grey text-caption">
|
||||
{{ $t(caption) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between no-wrap">
|
||||
<slot name="btnSlot">
|
||||
<q-btn icon="mdi-close" @click="modelValue = false" flat round />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q-px-none q-ma-none">
|
||||
<pn-shadow-scroll
|
||||
:height="bodyHeight"
|
||||
:bottomOffset
|
||||
:topOffset
|
||||
>
|
||||
<div class="q-pa-none q-ma-none">
|
||||
<q-resize-observer @resize="onInnerBodyResize" />
|
||||
<slot />
|
||||
</div>
|
||||
</pn-shadow-scroll>
|
||||
</div>
|
||||
|
||||
<div v-if="hasFooter" class="absolute-bottom">
|
||||
<q-resize-observer @resize="onFooterResize" />
|
||||
<div class="q-pa-md">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
<div :style="{ height: tgInsetBottom + 'px' }" />
|
||||
</div>
|
||||
</q-card>
|
||||
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { useSlots } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, useSlots } from 'vue'
|
||||
|
||||
const modelValue = defineModel<boolean>({
|
||||
required: true
|
||||
})
|
||||
|
||||
defineProps<{
|
||||
const modelValue = defineModel<boolean>({ required: true })
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
caption?: string
|
||||
maximized?: boolean
|
||||
}>(), { maximized: true })
|
||||
|
||||
const slots = useSlots()
|
||||
const cardHeaderRef = ref<HTMLElement | null>(null)
|
||||
const cardFooterInnerRef = ref<HTMLElement | null>(null)
|
||||
const cardBodyInnerRef = ref<HTMLElement | null>(null)
|
||||
const cardRef = ref()
|
||||
|
||||
const headerHeight = ref(0)
|
||||
const footerHeight = ref(0)
|
||||
const bodyInnerHeight = ref(0)
|
||||
const bodyHeight = ref(0)
|
||||
const tgInsetBottom = ref(0)
|
||||
const topOffset = 16
|
||||
|
||||
const updateDimensions = async () => {
|
||||
await nextTick(() => {
|
||||
headerHeight.value = cardHeaderRef.value?.offsetHeight || 0
|
||||
footerHeight.value = cardFooterInnerRef.value?.offsetHeight || 0
|
||||
bodyInnerHeight.value = cardBodyInnerRef.value?.offsetHeight || 0
|
||||
const needScroll = headerHeight.value + footerHeight.value + bodyInnerHeight.value > window.innerHeight - 48
|
||||
bodyHeight.value = needScroll
|
||||
? window.innerHeight - 48 - headerHeight.value - footerHeight.value
|
||||
: bodyInnerHeight.value + 16
|
||||
})
|
||||
const windowHeight = ref(window.innerHeight)
|
||||
|
||||
const hasFooter = computed(() => !!slots['footer'])
|
||||
const bottomOffset = computed(() => hasFooter.value ? footerHeight.value : tgInsetBottom.value)
|
||||
|
||||
const bodyHeight = computed(() => {
|
||||
// hack for update computed params on window resize
|
||||
const _trigger = windowHeight.value //eslint-disable-line
|
||||
const el = cardRef.value?.$el
|
||||
if (!el) return 0
|
||||
|
||||
const maxH = parseFloat(window.getComputedStyle(el).maxHeight) || 0
|
||||
|
||||
return props.maximized
|
||||
? maxH - headerHeight.value
|
||||
: Math.min(
|
||||
bodyInnerHeight.value + topOffset + (hasFooter.value ? footerHeight.value : 0),
|
||||
maxH - headerHeight.value
|
||||
)
|
||||
})
|
||||
|
||||
interface SizeParams { height: number; width: number }
|
||||
const onHeaderResize = (size: SizeParams) => headerHeight.value = size.height
|
||||
const onFooterResize = (size: SizeParams) => footerHeight.value = hasFooter.value ? size.height : 0
|
||||
const onInnerBodyResize = (size: SizeParams) => bodyInnerHeight.value = size.height
|
||||
|
||||
const handleWindowResize = () => {
|
||||
windowHeight.value = window.innerHeight
|
||||
}
|
||||
|
||||
watch(() => slots.body?.(), updateDimensions, { flush: 'post' })
|
||||
watch(modelValue, updateDimensions)
|
||||
const getTgInsetBottom = () => {
|
||||
const value = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--tg-safe-area-inset-bottom')
|
||||
.trim()
|
||||
|
||||
tgInsetBottom.value = parseFloat(value) || 0
|
||||
}
|
||||
|
||||
onMounted(() => getTgInsetBottom())
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleWindowResize)
|
||||
getTgInsetBottom()
|
||||
})
|
||||
|
||||
onUnmounted(() => window.removeEventListener('resize', handleWindowResize))
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user