feat(design): popover 支持点击外部关闭

新增 closeOnClickOutside 与 clickOutsideIgnore 配置,
兼容 element-plus / tdesign 衍生浮层。
历史列表面板改用 v-model:visible 配合自动收起。
This commit is contained in:
roymondchen 2026-06-11 16:49:54 +08:00
parent fd652b0d13
commit 846f05e04d
3 changed files with 85 additions and 2 deletions

View File

@ -43,8 +43,16 @@ const props = withDefaults(defineProps<PopoverProps>(), {
visible: undefined,
tabindex: 0,
destroyOnClose: false,
closeOnClickOutside: true,
});
const emit = defineEmits<{
/** 受控模式(传入了 visible下点击外部收起时触发便于配合 v-model:visible。 */
'update:visible': [_visible: boolean];
/** 点击 popover 及其衍生浮层以外的区域时触发。 */
clickoutside: [_event: MouseEvent];
}>();
const popoverVisible = ref(false);
const visibleWatch = watch(
@ -179,6 +187,70 @@ if (props.trigger === 'hover' && typeof props.visible === 'undefined') {
});
}
/**
* popover 内部触发却挂载到 body popper 之外的浮层弹窗二次确认框tooltip
* 下拉 / 日期选择等点击它们属于 popover 内部交互不应顺带把 popover 关闭
*
* 由于 @tmagic/design 通过适配器支持 element-plustdesign 等多套 UI 这里同时列出
* 两套库的浮层 classclass 名互不冲突未命中的选择器无副作用避免切换适配器后失效
*/
const DEFAULT_CLICK_OUTSIDE_IGNORE = [
// @tmagic/design
'.tmagic-design-dialog',
// element-plus
'.el-overlay',
'.el-message-box',
'.el-popper',
'.el-select-dropdown',
'.el-picker__popper',
'.el-dropdown__popper',
'.el-cascader__dropdown',
// tdesign / DialogPlugin / MessagePlugintooltip / select / dropdown / .t-popup
'.t-dialog__ctx',
'.t-dialog',
'.t-message',
'.t-popup',
].join(',');
const clickOutsideIgnoreSelector = computed(() =>
[DEFAULT_CLICK_OUTSIDE_IGNORE, props.clickOutsideIgnore].filter(Boolean).join(','),
);
const handleClickOutside = (e: MouseEvent) => {
if (props.disabled) return;
const target = e.target as HTMLElement | null;
if (!target) return;
// referencepopper
if (referenceElementRef.value?.contains(target)) return;
if (popperElementRef.value?.contains(target)) return;
if (target.closest(clickOutsideIgnoreSelector.value)) return;
emit('clickoutside', e);
// update:visible v-model:visible
if (typeof props.visible === 'undefined') {
popoverVisible.value = false;
} else {
emit('update:visible', false);
}
};
const bindClickOutside = () => globalThis.document?.addEventListener('click', handleClickOutside);
const unbindClickOutside = () => globalThis.document?.removeEventListener('click', handleClickOutside);
watch(popoverVisible, (visible) => {
if (!props.closeOnClickOutside) return;
if (visible) {
// popover
nextTick(bindClickOutside);
} else {
unbindClickOutside();
}
});
const destroy = () => {
if (!instanceRef.value) return;
@ -188,5 +260,6 @@ const destroy = () => {
onBeforeUnmount(() => {
destroy();
unbindClickOutside();
});
</script>

View File

@ -258,6 +258,13 @@ export interface PopoverProps {
popperClass?: string;
tabindex?: number;
destroyOnClose?: boolean;
/** 点击 popover 及其衍生浮层以外的区域时收起,默认开启。 */
closeOnClickOutside?: boolean;
/**
* / /
* popover body popover
*/
clickOutsideIgnore?: string;
}
export interface RadioProps {

View File

@ -3,7 +3,7 @@
popper-class="m-editor-history-list-popover"
placement="bottom"
trigger="click"
:visible="visible"
v-model:visible="visible"
:width="660"
>
<div class="m-editor-history-list">
@ -163,7 +163,10 @@ const ClockIcon = markRaw(Clock);
const CloseIcon = markRaw(Close);
const activeTab = ref<string>('page');
/** 面板显隐受控reference 图标点击切换,右上角关闭按钮置为 false。 */
/**
* 面板显隐受控reference 图标点击切换右上角关闭按钮置为 false
* 点击面板以外区域的自动收起由 TMagicPopover 通过 v-model:visible 回写完成
*/
const visible = ref(false);
const tabPaneComponent = getDesignConfig('components')?.tabPane;