feat: support unlocking scroll when modal dialog closes

This commit is contained in:
Harold Zhang
2026-06-08 14:15:31 +08:00
parent f23c9f5e19
commit c2a85f4ec2
2 changed files with 36 additions and 39 deletions

View File

@@ -5,9 +5,8 @@ import type { HTMLAttributes } from 'vue';
import type { ClassType } from '@vben-core/typings'; import type { ClassType } from '@vben-core/typings';
import { computed, inject, ref } from 'vue'; import { computed, ref } from 'vue';
import { useScrollLock } from '@vben-core/composables';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { X } from '@lucide/vue'; import { X } from '@lucide/vue';
@@ -18,6 +17,8 @@ import {
useForwardPropsEmits, useForwardPropsEmits,
} from 'reka-ui'; } from 'reka-ui';
import DialogOverlay from './DialogOverlay.vue';
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); });
@@ -73,11 +74,9 @@ const position = computed(() => {
return isAppendToBody() ? 'fixed' : 'absolute'; return isAppendToBody() ? 'fixed' : 'absolute';
}); });
// reka-ui 的 Dialog 在 modal=false 时不会渲染遮罩,这里自行渲染一个遮罩层并锁定滚动, // reka-ui 的 Dialog 在 modal=false 时不会渲染遮罩,这里通过 DialogOverlay 组件自行渲染遮罩层并锁定滚动。
// 既保留遮罩/滚动锁定能力,又避免 modal=true 时 body 被设置 pointer-events:none 导致 // DialogOverlay 组件通过 v-if 控制挂载/卸载,其内部的 useScrollLock 会在组件卸载时自动解锁滚动,
// 弹出层(如 Select 下拉框)无法点击的问题。 // 避免 modal=true 时 body 被设置 pointer-events:none 导致弹出层(如 Select 下拉框)无法点击的问题。
useScrollLock();
const dismissableModalId = inject('DISMISSABLE_MODAL_ID', undefined);
const forwarded = useForwardPropsEmits(delegatedProps, emits); const forwarded = useForwardPropsEmits(delegatedProps, emits);
@@ -100,18 +99,13 @@ defineExpose({
<template> <template>
<DialogPortal :to="appendTo"> <DialogPortal :to="appendTo">
<Transition name="fade"> <Transition name="fade">
<div <DialogOverlay
v-if="open && modal" v-if="open && modal"
:data-dismissable-modal="dismissableModalId" :overlay-blur="overlayBlur"
:style="{ :position="position"
...(zIndex ? { zIndex } : {}), :z-index="zIndex"
position,
backdropFilter:
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
}"
:class="cn('z-popup bg-overlay inset-0 fixed')"
@click="() => emits('close')" @click="() => emits('close')"
></div> />
</Transition> </Transition>
<DialogContent <DialogContent
ref="contentRef" ref="contentRef"

View File

@@ -1,31 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogOverlayProps } from 'reka-ui'; import { inject } from 'vue';
import type { HTMLAttributes } from 'vue';
import { useScrollLock } from '@vben-core/composables';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { reactiveOmit } from '@vueuse/core'; const props = withDefaults(
import { DialogOverlay } from 'reka-ui'; defineProps<{
class?: any;
overlayBlur?: number;
position?: 'absolute' | 'fixed';
zIndex?: number;
}>(),
{
position: 'fixed',
},
);
const props = defineProps< useScrollLock();
DialogOverlayProps & { class?: HTMLAttributes['class'] } const dismissableModalId = inject('DISMISSABLE_MODAL_ID', undefined);
>();
const delegatedProps = reactiveOmit(props, 'class');
</script> </script>
<template> <template>
<DialogOverlay <div
data-slot="dialog-overlay" :data-dismissable-modal="dismissableModalId"
v-bind="delegatedProps" :style="{
:class=" ...(zIndex ? { zIndex } : {}),
cn( position,
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', backdropFilter:
props.class, overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
) }"
" :class="cn('z-popup bg-overlay inset-0 fixed', props.class)"
> ></div>
<slot></slot>
</DialogOverlay>
</template> </template>