elk/components/modal/ModalDialog.vue

217 lines
5.0 KiB
Vue
Raw Normal View History

2022-12-02 07:02:44 +00:00
<script lang="ts" setup>
import { useDeactivated } from '~/composables/lifecycle'
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
export interface Props {
/** v-model dislog visibility */
modelValue: boolean
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
/**
* level of depth
*
* @default 100
*/
zIndex?: number
/**
* whether to allow close dialog by clicking mask layer
*
* @default true
*/
closeByMask?: boolean
/**
* use v-if, destroy all the internal elements after closed
*
* @default true
*/
useVIf?: boolean
/**
* keep the dialog opened even when in other views
*
* @default false
2022-12-02 07:27:44 +00:00
*/
2022-12-02 07:02:44 +00:00
keepAlive?: boolean
/** customizable class for the div outside of slot */
customClass?: string
}
2022-12-02 07:27:44 +00:00
2022-12-02 07:02:44 +00:00
const props = withDefaults(defineProps<Props>(), {
zIndex: 100,
closeByMask: true,
useVIf: true,
keepAlive: false,
})
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
const emits = defineEmits<{
/** v-model dislog visibility */
(event: 'update:modelValue', value: boolean): void
2022-11-23 03:48:01 +00:00
}>()
2022-11-24 08:04:53 +00:00
2022-12-02 07:02:44 +00:00
const visible = useVModel(props, 'modelValue', emits, { passive: true })
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
const deactivated = useDeactivated()
const route = useRoute()
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
/** scrollable HTML element */
const elDialogScroll = ref<HTMLDivElement>()
const elDialogMain = ref<HTMLDivElement>()
const elDialogRoot = ref<HTMLDivElement>()
2022-11-27 01:52:46 +00:00
2022-12-02 07:02:44 +00:00
defineExpose({
elDialogRoot,
elDialogMain,
elDialogScroll,
})
/** close the dialog */
2022-11-27 03:13:39 +00:00
function close() {
2022-12-02 07:02:44 +00:00
visible.value = false
}
function clickMask() {
if (props.closeByMask)
close()
2022-11-27 03:13:39 +00:00
}
2022-12-02 07:02:44 +00:00
const routePath = ref(route.path)
watch(visible, (value) => {
if (value)
routePath.value = route.path
})
const notInCurrentPage = computed(() => deactivated.value || routePath.value !== route.path)
watch(notInCurrentPage, (value) => {
if (props.keepAlive)
return
if (value)
close()
})
// controls the state of v-if.
// when useVIf is toggled, v-if has the same state as modelValue, otherwise v-if is true
const isVIf = computed(() => {
return props.useVIf
? visible.value
: true
})
// controls the state of v-show.
// when useVIf is toggled, v-show is true, otherwise it has the same state as modelValue
const isVShow = computed(() => {
return !props.useVIf
? visible.value
: true
})
const bindTypeToAny = ($attrs: any) => $attrs as any
2022-11-27 03:13:39 +00:00
useEventListener('keydown', (e: KeyboardEvent) => {
2022-12-02 07:02:44 +00:00
if (!visible.value)
2022-11-27 03:13:39 +00:00
return
if (e.key === 'Escape') {
close()
e.preventDefault()
}
2022-11-24 08:04:53 +00:00
})
2022-12-02 07:02:44 +00:00
</script>
2022-11-27 03:13:39 +00:00
2022-12-02 07:02:44 +00:00
<script lang="ts">
export default {
inheritAttrs: false,
2022-11-27 03:13:39 +00:00
}
2022-11-23 03:48:01 +00:00
</script>
<template>
<SafeTeleport to="#teleport-end">
2022-12-02 07:02:44 +00:00
<!-- Dialog component -->
<Transition name="dialog-visible">
<div
2022-12-02 07:02:44 +00:00
v-if="isVIf"
v-show="isVShow"
ref="elDialogRoot"
:style="{
'z-index': zIndex,
}"
2022-12-02 07:27:44 +00:00
class="scrollbar-hide"
fixed inset-0 overflow-y-auto overscroll-none
>
2022-12-02 07:02:44 +00:00
<!-- The style `scrollbar-hide overscroll-none overflow-y-scroll` and `h="[calc(100%+0.5px)]"` is used to implement scroll locking, -->
<!-- corresponding to issue: #106, so please don't remove it. -->
<!-- Mask layer: blur -->
<div class="dialog-mask" absolute inset-0 z-0 bg-transparent opacity-100 backdrop-filter backdrop-blur-sm touch-none />
<!-- Mask layer: dimming -->
<div class="dialog-mask" absolute inset-0 z-0 bg-black opacity-48 touch-none h="[calc(100%+0.5px)]" @click="clickMask" />
<!-- Dialog container -->
<div class="p-safe-area" absolute inset-0 z-1 pointer-events-none opacity-100 flex>
<div class="flex-1 flex items-center justify-center p-4">
2022-12-02 08:06:52 +00:00
<!-- We use `class` here to make v-bind being able to be override them -->
2022-12-02 07:02:44 +00:00
<div
ref="elDialogMain"
2022-12-02 08:01:56 +00:00
class="dialog-main w-full rounded shadow-lg pointer-events-auto isolate bg-base border-base border-1px border-solid w-full max-w-125 max-h-full flex flex-col"
2022-12-02 07:02:44 +00:00
v-bind="bindTypeToAny($attrs)"
>
<!-- header -->
<slot name="header" />
<!-- main -->
2022-12-02 07:27:44 +00:00
<div
ref="elDialogScroll"
2022-12-02 08:01:56 +00:00
class="overflow-y-auto touch-pan-y touch-pan-x overscroll-none flex-1"
2022-12-02 07:27:44 +00:00
:class="customClass"
>
2022-12-02 07:02:44 +00:00
<slot />
</div>
<!-- footer -->
<slot name="footer" />
</div>
</div>
</div>
</div>
2022-12-02 07:02:44 +00:00
</Transition>
</SafeTeleport>
2022-11-23 03:48:01 +00:00
</template>
2022-11-27 08:02:09 +00:00
2022-12-02 07:02:44 +00:00
<style lang="postcss" scoped>
.dialog-visible-enter-active,
.dialog-visible-leave-active {
transition-duration: 0.25s;
.dialog-mask {
transition: opacity 0.25s ease;
}
.dialog-main {
transition: opacity 0.25s ease, transform 0.25s ease;
}
}
.dialog-visible-enter-from,
.dialog-visible-leave-to {
.dialog-mask {
opacity: 0;
}
.dialog-main {
transform: translateY(50px);
opacity: 0;
}
}
.p-safe-area {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
2022-11-27 08:02:09 +00:00
.scrollbar-hide {
scrollbar-width: none;
}
2022-11-29 20:37:45 +00:00
2022-11-27 08:02:09 +00:00
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>