From 318201056285e0781d1e96ef07a1364d4ea0d558 Mon Sep 17 00:00:00 2001 From: Zhong Date: Wed, 30 Jul 2025 16:51:18 +0800 Subject: [PATCH] feat(components): [dialog] add `modal-penetrable` prop (#21511) * feat(components): [dialog] add `modal-penetrable` prop * refactor(components): [dialog] optimize code * test(components): [dialog] update test * test(components): [test] update test --- docs/en-US/component/dialog.md | 1 + .../dialog/__tests__/dialog.test.tsx | 23 +++++++++++++++++++ packages/components/dialog/src/dialog.ts | 4 ++++ packages/components/dialog/src/dialog.vue | 10 +++++++- packages/theme-chalk/src/dialog.scss | 10 ++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/en-US/component/dialog.md b/docs/en-US/component/dialog.md index ef498468e1..96fc288670 100644 --- a/docs/en-US/component/dialog.md +++ b/docs/en-US/component/dialog.md @@ -173,6 +173,7 @@ dialog/events | fullscreen | whether the Dialog takes up full screen | ^[boolean] | false | | top | value for `margin-top` of Dialog CSS, default is 15vh | ^[string] | '' | | modal | whether a mask is displayed | ^[boolean] | true | +| modal-penetrable ^(2.10.5) | whether the mask is penetrable. The modal attribute must be `false`. | ^[boolean] | false | | modal-class | custom class names for mask | ^[string] | — | | header-class ^(2.9.3) | custom class names for header wrapper | ^[string] | — | | body-class ^(2.9.3) | custom class names for body wrapper | ^[string] | — | diff --git a/packages/components/dialog/__tests__/dialog.test.tsx b/packages/components/dialog/__tests__/dialog.test.tsx index 9f29605b09..a0e76f3212 100644 --- a/packages/components/dialog/__tests__/dialog.test.tsx +++ b/packages/components/dialog/__tests__/dialog.test.tsx @@ -225,6 +225,29 @@ describe('Dialog.vue', () => { await triggerCompositeClick(wrapper.find('.el-overlay-dialog')) expect(wrapper.vm.visible).toBe(false) }) + + test('should not click the mask to close when it is penetrable', async () => { + const onClick = vi.fn() + + const wrapper = mount(() => ( + <> + + {AXIOM} + + + + )) + + const overlay = wrapper.findComponent({ name: 'ElOverlay' }) + const dialog = wrapper.findComponent({ name: 'ElDialog' }) + expect(overlay.exists()).toBe(true) + expect(overlay.classes()).toContain('is-penetrable') + + await overlay.trigger('click') + await wrapper.find('button').trigger('click') + expect(dialog.vm.visible).toBe(true) + expect(onClick).toHaveBeenCalled() + }) }) describe('life cycles', () => { diff --git a/packages/components/dialog/src/dialog.ts b/packages/components/dialog/src/dialog.ts index 2e7ff22c75..48782e61c1 100644 --- a/packages/components/dialog/src/dialog.ts +++ b/packages/components/dialog/src/dialog.ts @@ -68,6 +68,10 @@ export const dialogProps = buildProps({ type: Boolean, default: true, }, + /** + * @description whether the mask is penetrable + */ + modalPenetrable: Boolean, /** * @description the Time(milliseconds) before open */ diff --git a/packages/components/dialog/src/dialog.vue b/packages/components/dialog/src/dialog.vue index 091c430fa5..cac2cddb0f 100644 --- a/packages/components/dialog/src/dialog.vue +++ b/packages/components/dialog/src/dialog.vue @@ -8,7 +8,11 @@ v-show="visible" custom-mask-event :mask="modal" - :overlay-class="modalClass" + :overlay-class="[ + modalClass ?? '', + `${ns.namespace.value}-modal-dialog`, + ns.is('penetrable', penetrable), + ]" :z-index="zIndex" >
props.draggable && !props.fullscreen) +const penetrable = computed( + () => props.modalPenetrable && !props.modal && !props.fullscreen +) + const resetPosition = () => { dialogContentRef.value?.resetPosition() } diff --git a/packages/theme-chalk/src/dialog.scss b/packages/theme-chalk/src/dialog.scss index b8d2a4dc85..da97787d79 100644 --- a/packages/theme-chalk/src/dialog.scss +++ b/packages/theme-chalk/src/dialog.scss @@ -128,6 +128,16 @@ } } +.#{$namespace}-modal-dialog { + &.is-penetrable { + pointer-events: none; + + .#{$namespace}-dialog { + pointer-events: auto; + } + } +} + .#{$namespace}-overlay-dialog { position: fixed; top: 0;