fix: modal 下拉等不能选中
parent
564b349700
commit
c816591b69
|
|
@ -1,7 +1,16 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExtendedModalApi, ModalProps } from './modal';
|
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 { usePriorityValues, useSimpleLocale } from '@vben-core/composables';
|
||||||
import { Expand, Shrink } from '@vben-core/icons';
|
import { Expand, Shrink } from '@vben-core/icons';
|
||||||
|
|
@ -47,6 +56,10 @@ const footerRef = ref();
|
||||||
const { $t } = useSimpleLocale();
|
const { $t } = useSimpleLocale();
|
||||||
const state = props.modalApi?.useStore?.();
|
const state = props.modalApi?.useStore?.();
|
||||||
|
|
||||||
|
const id = useId();
|
||||||
|
// 遮罩层通过该 id 标记,仅当点击发生在当前 Modal 的遮罩上时才允许关闭
|
||||||
|
provide('DISMISSABLE_MODAL_ID', id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
appendToMain,
|
appendToMain,
|
||||||
bordered,
|
bordered,
|
||||||
|
|
@ -181,8 +194,15 @@ function handleOpenAutoFocus(e: Event) {
|
||||||
|
|
||||||
// pointer-down-outside
|
// pointer-down-outside
|
||||||
function pointerDownOutside(e: Event) {
|
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.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +232,7 @@ function handleClosed() {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
:modal="modal"
|
:modal="false"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
@update:open="() => (!submitting ? modalApi?.close() : undefined)"
|
@update:open="() => (!submitting ? modalApi?.close() : undefined)"
|
||||||
>
|
>
|
||||||
|
|
@ -240,7 +260,7 @@ function handleClosed() {
|
||||||
:force-mount="getForceMount"
|
:force-mount="getForceMount"
|
||||||
:modal="modal"
|
:modal="modal"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
:show-close="closable"
|
:show-close-button="closable"
|
||||||
:animation-type="animationType"
|
:animation-type="animationType"
|
||||||
:z-index="zIndex"
|
:z-index="zIndex"
|
||||||
:overlay-blur="overlayBlur"
|
:overlay-blur="overlayBlur"
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,39 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
|
||||||
|
|
||||||
|
import type { HTMLAttributes } from 'vue';
|
||||||
|
|
||||||
import type { ClassType } from '@vben-core/typings';
|
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 { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { X } from '@lucide/vue';
|
import { X } from '@lucide/vue';
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogOverlay,
|
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from 'reka-ui';
|
} from 'reka-ui';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<
|
defineProps<
|
||||||
DialogContentProps & {
|
DialogContentProps & {
|
||||||
animationType?: 'scale' | 'slide';
|
animationType?: 'scale' | 'slide';
|
||||||
appendTo?: HTMLElement | string;
|
appendTo?: HTMLElement | string;
|
||||||
class?: ClassType;
|
class?: HTMLAttributes['class'];
|
||||||
closeClass?: ClassType;
|
closeClass?: ClassType;
|
||||||
closeDisabled?: boolean;
|
closeDisabled?: boolean;
|
||||||
modal?: boolean;
|
modal?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
overlayBlur?: number;
|
overlayBlur?: number;
|
||||||
showClose?: boolean;
|
showCloseButton?: boolean;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
}
|
}
|
||||||
>(),
|
>(),
|
||||||
|
|
@ -35,7 +41,7 @@ const props = withDefaults(
|
||||||
appendTo: 'body',
|
appendTo: 'body',
|
||||||
animationType: 'slide',
|
animationType: 'slide',
|
||||||
closeDisabled: false,
|
closeDisabled: false,
|
||||||
showClose: true,
|
showCloseButton: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const emits = defineEmits<
|
const emits = defineEmits<
|
||||||
|
|
@ -47,7 +53,7 @@ const delegatedProps = computed(() => {
|
||||||
class: _,
|
class: _,
|
||||||
modal: _modal,
|
modal: _modal,
|
||||||
open: _open,
|
open: _open,
|
||||||
showClose: __,
|
showCloseButton: __,
|
||||||
animationType: ___,
|
animationType: ___,
|
||||||
...delegated
|
...delegated
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -67,6 +73,12 @@ const position = computed(() => {
|
||||||
return isAppendToBody() ? 'fixed' : 'absolute';
|
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 forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
||||||
|
|
@ -87,23 +99,24 @@ defineExpose({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal :to="appendTo">
|
<DialogPortal :to="appendTo">
|
||||||
<DialogOverlay
|
<Transition name="fade">
|
||||||
v-if="open && modal"
|
<div
|
||||||
:style="{
|
v-if="open && modal"
|
||||||
...(zIndex ? { zIndex } : {}),
|
:data-dismissable-modal="dismissableModalId"
|
||||||
position,
|
:style="{
|
||||||
backdropFilter:
|
...(zIndex ? { zIndex } : {}),
|
||||||
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
position,
|
||||||
}"
|
backdropFilter:
|
||||||
:class="
|
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||||
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',
|
:class="cn('z-popup bg-overlay inset-0 fixed')"
|
||||||
)
|
@click="() => emits('close')"
|
||||||
"
|
></div>
|
||||||
/>
|
</Transition>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
ref="contentRef"
|
ref="contentRef"
|
||||||
:style="{ ...(zIndex ? { zIndex } : {}), position }"
|
:style="{ ...(zIndex ? { zIndex } : {}), position }"
|
||||||
|
data-slot="dialog-content"
|
||||||
@animationend="onAnimationEnd"
|
@animationend="onAnimationEnd"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="
|
||||||
|
|
@ -120,8 +133,9 @@ defineExpose({
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
||||||
<DialogClose
|
<DialogClose
|
||||||
v-if="showClose"
|
v-if="showCloseButton"
|
||||||
:disabled="closeDisabled"
|
:disabled="closeDisabled"
|
||||||
|
data-slot="dialog-close"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
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',
|
'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')"
|
@click="() => emits('close')"
|
||||||
>
|
>
|
||||||
<X class="h-4 w-4" />
|
<X class="size-4" />
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,31 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogOverlayProps } from 'reka-ui';
|
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<DialogOverlayProps>();
|
const props = defineProps<
|
||||||
|
DialogOverlayProps & { class?: HTMLAttributes['class'] }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, 'class');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<slot></slot>
|
||||||
</DialogOverlay>
|
</DialogOverlay>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
<DialogClose
|
<DialogClose
|
||||||
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
|
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>
|
<span class="sr-only">Close</span>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue