fix: modal 下拉等不能选中

master^2
xingyu4j 2026-06-06 19:32:31 +08:00
parent 564b349700
commit c816591b69
4 changed files with 81 additions and 29 deletions

View File

@ -1,7 +1,16 @@
<script lang="ts" setup>
import type { ExtendedModalApi, ModalProps } from './modal';
import { computed, nextTick, onDeactivated, ref, unref, watch } from 'vue';
import {
computed,
nextTick,
onDeactivated,
provide,
ref,
unref,
useId,
watch,
} from 'vue';
import { usePriorityValues, useSimpleLocale } from '@vben-core/composables';
import { Expand, Shrink } from '@vben-core/icons';
@ -47,6 +56,10 @@ const footerRef = ref();
const { $t } = useSimpleLocale();
const state = props.modalApi?.useStore?.();
const id = useId();
// id Modal
provide('DISMISSABLE_MODAL_ID', id);
const {
appendToMain,
bordered,
@ -181,8 +194,15 @@ function handleOpenAutoFocus(e: Event) {
// pointer-down-outside
function pointerDownOutside(e: Event) {
if (!closeOnClickModal.value || submitting.value) {
const target = e.target as HTMLElement;
const isDismissableModal = target?.dataset.dismissableModal;
if (
!closeOnClickModal.value ||
isDismissableModal !== id ||
submitting.value
) {
e.preventDefault();
e.stopPropagation();
}
}
@ -212,7 +232,7 @@ function handleClosed() {
</script>
<template>
<Dialog
:modal="modal"
:modal="false"
:open="state?.isOpen"
@update:open="() => (!submitting ? modalApi?.close() : undefined)"
>
@ -240,7 +260,7 @@ function handleClosed() {
:force-mount="getForceMount"
:modal="modal"
:open="state?.isOpen"
:show-close="closable"
:show-close-button="closable"
:animation-type="animationType"
:z-index="zIndex"
:overlay-blur="overlayBlur"

View File

@ -1,33 +1,39 @@
<script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
import type { HTMLAttributes } from 'vue';
import type { ClassType } from '@vben-core/typings';
import { computed, ref } from 'vue';
import { computed, inject, ref } from 'vue';
import { useScrollLock } from '@vben-core/composables';
import { cn } from '@vben-core/shared/utils';
import { X } from '@lucide/vue';
import {
DialogClose,
DialogContent,
DialogOverlay,
DialogPortal,
useForwardPropsEmits,
} from 'reka-ui';
defineOptions({
inheritAttrs: false,
});
const props = withDefaults(
defineProps<
DialogContentProps & {
animationType?: 'scale' | 'slide';
appendTo?: HTMLElement | string;
class?: ClassType;
class?: HTMLAttributes['class'];
closeClass?: ClassType;
closeDisabled?: boolean;
modal?: boolean;
open?: boolean;
overlayBlur?: number;
showClose?: boolean;
showCloseButton?: boolean;
zIndex?: number;
}
>(),
@ -35,7 +41,7 @@ const props = withDefaults(
appendTo: 'body',
animationType: 'slide',
closeDisabled: false,
showClose: true,
showCloseButton: true,
},
);
const emits = defineEmits<
@ -47,7 +53,7 @@ const delegatedProps = computed(() => {
class: _,
modal: _modal,
open: _open,
showClose: __,
showCloseButton: __,
animationType: ___,
...delegated
} = props;
@ -67,6 +73,12 @@ const position = computed(() => {
return isAppendToBody() ? 'fixed' : 'absolute';
});
// reka-ui Dialog modal=false
// / modal=true body pointer-events:none
// Select
useScrollLock();
const dismissableModalId = inject('DISMISSABLE_MODAL_ID', undefined);
const forwarded = useForwardPropsEmits(delegatedProps, emits);
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
@ -87,23 +99,24 @@ defineExpose({
<template>
<DialogPortal :to="appendTo">
<DialogOverlay
v-if="open && modal"
:style="{
...(zIndex ? { zIndex } : {}),
position,
backdropFilter:
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
}"
: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',
)
"
/>
<Transition name="fade">
<div
v-if="open && modal"
:data-dismissable-modal="dismissableModalId"
:style="{
...(zIndex ? { zIndex } : {}),
position,
backdropFilter:
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
}"
:class="cn('z-popup bg-overlay inset-0 fixed')"
@click="() => emits('close')"
></div>
</Transition>
<DialogContent
ref="contentRef"
:style="{ ...(zIndex ? { zIndex } : {}), position }"
data-slot="dialog-content"
@animationend="onAnimationEnd"
v-bind="forwarded"
:class="
@ -120,8 +133,9 @@ defineExpose({
<slot></slot>
<DialogClose
v-if="showClose"
v-if="showCloseButton"
:disabled="closeDisabled"
data-slot="dialog-close"
:class="
cn(
'flex-center text-foreground/80 hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-3 right-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none',
@ -130,7 +144,7 @@ defineExpose({
"
@click="() => emits('close')"
>
<X class="h-4 w-4" />
<X class="size-4" />
</DialogClose>
</DialogContent>
</DialogPortal>

View File

@ -1,13 +1,31 @@
<script setup 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';
const props = defineProps<DialogOverlayProps>();
const props = defineProps<
DialogOverlayProps & { class?: HTMLAttributes['class'] }
>();
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<DialogOverlay data-slot="dialog-overlay" v-bind="props">
<DialogOverlay
data-slot="dialog-overlay"
v-bind="delegatedProps"
:class="
cn(
'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',
props.class,
)
"
>
<slot></slot>
</DialogOverlay>
</template>

View File

@ -60,7 +60,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<DialogClose
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
>
<X class="w-4 h-4" />
<X class="size-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>