Merge branch 'fork/HaroldZhangCode91/feature/fix_freeze_scrollbar_of_modal'

pull/355/head^2
金毛88 2026-06-04 13:54:21 +08:00
commit 6544340d80
3 changed files with 33 additions and 72 deletions

View File

@ -1,16 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ExtendedModalApi, ModalProps } from './modal'; import type { ExtendedModalApi, ModalProps } from './modal';
import { import { computed, nextTick, onDeactivated, ref, unref, watch } from 'vue';
computed,
nextTick,
onDeactivated,
provide,
ref,
unref,
useId,
watch,
} from 'vue';
import { usePriorityValues, useSimpleLocale } from '@vben-core/composables'; import { usePriorityValues, useSimpleLocale } from '@vben-core/composables';
import { Expand, Shrink } from '@vben-core/icons'; import { Expand, Shrink } from '@vben-core/icons';
@ -53,10 +44,6 @@ const headerRef = ref();
// @ts-expect-error unused // @ts-expect-error unused
const footerRef = ref(); const footerRef = ref();
const id = useId();
provide('DISMISSABLE_MODAL_ID', id);
const { $t } = useSimpleLocale(); const { $t } = useSimpleLocale();
const state = props.modalApi?.useStore?.(); const state = props.modalApi?.useStore?.();
@ -194,15 +181,8 @@ function handleOpenAutoFocus(e: Event) {
// pointer-down-outside // pointer-down-outside
function pointerDownOutside(e: Event) { function pointerDownOutside(e: Event) {
const target = e.target as HTMLElement; if (!closeOnClickModal.value || submitting.value) {
const isDismissableModal = target?.dataset.dismissableModal;
if (
!closeOnClickModal.value ||
isDismissableModal !== id ||
submitting.value
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
} }
} }
@ -211,6 +191,10 @@ function handleFocusOutside(e: Event) {
e.stopPropagation(); e.stopPropagation();
} }
function handleCloseAutoFocus(_e: Event) {
// allow reka-ui to return focus to the trigger element on close
}
const getForceMount = computed(() => { const getForceMount = computed(() => {
return !unref(destroyOnClose) && unref(firstOpened); return !unref(destroyOnClose) && unref(firstOpened);
}); });
@ -228,7 +212,7 @@ function handleClosed() {
</script> </script>
<template> <template>
<Dialog <Dialog
:modal="false" :modal="modal"
:open="state?.isOpen" :open="state?.isOpen"
@update:open="() => (!submitting ? modalApi?.close() : undefined)" @update:open="() => (!submitting ? modalApi?.close() : undefined)"
> >
@ -261,7 +245,7 @@ function handleClosed() {
:z-index="zIndex" :z-index="zIndex"
:overlay-blur="overlayBlur" :overlay-blur="overlayBlur"
close-class="top-3" close-class="top-3"
@close-auto-focus="handleFocusOutside" @close-auto-focus="handleCloseAutoFocus"
@closed="handleClosed" @closed="handleClosed"
:close-disabled="submitting" :close-disabled="submitting"
@escape-key-down="escapeKeyDown" @escape-key-down="escapeKeyDown"

View File

@ -5,13 +5,16 @@ import type { ClassType } from '@vben-core/typings';
import { computed, 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';
import { DialogClose, DialogContent, useForwardPropsEmits } from 'reka-ui'; import {
DialogClose,
import DialogOverlay from './DialogOverlay.vue'; DialogContent,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'reka-ui';
const props = withDefaults( const props = withDefaults(
defineProps< defineProps<
@ -64,8 +67,6 @@ const position = computed(() => {
return isAppendToBody() ? 'fixed' : 'absolute'; return isAppendToBody() ? 'fixed' : 'absolute';
}); });
useScrollLock();
const forwarded = useForwardPropsEmits(delegatedProps, emits); const forwarded = useForwardPropsEmits(delegatedProps, emits);
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null); const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
@ -85,19 +86,21 @@ defineExpose({
</script> </script>
<template> <template>
<Teleport defer :to="appendTo"> <DialogPortal :to="appendTo">
<Transition name="fade"> <DialogOverlay
<DialogOverlay v-if="open && modal"
v-if="open && modal" :style="{
:style="{ ...(zIndex ? { zIndex } : {}),
...(zIndex ? { zIndex } : {}), position,
position, backdropFilter:
backdropFilter: overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none', }"
}" :class="
@click="() => emits('close')" cn(
/> 'z-popup bg-overlay inset-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed',
</Transition> )
"
/>
<DialogContent <DialogContent
ref="contentRef" ref="contentRef"
:style="{ ...(zIndex ? { zIndex } : {}), position }" :style="{ ...(zIndex ? { zIndex } : {}), position }"
@ -130,5 +133,5 @@ defineExpose({
<X class="h-4 w-4" /> <X class="h-4 w-4" />
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</Teleport> </DialogPortal>
</template> </template>

View File

@ -1,31 +1,5 @@
<script setup lang="ts"> <script lang="ts">
import type { DialogOverlayProps } from 'reka-ui';
import type { HTMLAttributes } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { reactiveOmit } from '@vueuse/core';
import { DialogOverlay } from 'reka-ui'; import { DialogOverlay } from 'reka-ui';
const props = defineProps< export default DialogOverlay;
DialogOverlayProps & { class?: HTMLAttributes['class'] }
>();
const delegatedProps = reactiveOmit(props, 'class');
</script> </script>
<template>
<DialogOverlay
data-slot="dialog-overlay"
v-bind="delegatedProps"
:class="
cn(
'z-popup bg-overlay inset-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed',
props.class,
)
"
>
<slot></slot>
</DialogOverlay>
</template>