mirror of
https://github.com/element-plus/element-plus.git
synced 2025-03-01 15:35:51 +08:00
fix(popper): remove clickouside listener when manual enabled (#450)
This commit is contained in:
parent
8d2d6be085
commit
8e95db293c
@ -97,7 +97,7 @@ describe('Dialog.vue', () => {
|
||||
test('should not have overlay mask when mask is false', () => {
|
||||
const wrapper = _mount({
|
||||
props: {
|
||||
mask: false,
|
||||
modal: false,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -1,14 +1,21 @@
|
||||
import {
|
||||
createVNode,
|
||||
defineComponent,
|
||||
Fragment,
|
||||
Transition,
|
||||
Teleport,
|
||||
h,
|
||||
withDirectives,
|
||||
vShow,
|
||||
toDisplayString,
|
||||
renderSlot,
|
||||
withCtx,
|
||||
} from 'vue'
|
||||
|
||||
import { TrapFocus } from '@element-plus/directives'
|
||||
import { stop } from '@element-plus/utils/dom'
|
||||
import { isValidWidthUnit } from '@element-plus/utils/validators'
|
||||
import { PatchFlags, renderBlock, renderIf } from '@element-plus/utils/vnode'
|
||||
|
||||
import ElOverlay from '@element-plus/overlay'
|
||||
import {
|
||||
@ -22,6 +29,13 @@ import {
|
||||
|
||||
import type { PropType, SetupContext } from 'vue'
|
||||
|
||||
const closeIcon = createVNode('i', { class: 'el-dialog__close el-icon el-icon-close' }, null, PatchFlags.HOISTED)
|
||||
const headerKls = { class: 'el-dialog__header' }
|
||||
const bodyKls = { class: 'el-dialog__body' }
|
||||
const titleKls = { class: 'el-dialog__title' }
|
||||
const footerKls = { class: 'el-dialog__footer', key: 0 }
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElDialog',
|
||||
props: {
|
||||
@ -114,44 +128,38 @@ export default defineComponent({
|
||||
return null
|
||||
}
|
||||
const { $slots } = this
|
||||
const closeBtn = this.showClose
|
||||
? h(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
class: 'el-dialog__headerbtn',
|
||||
ariaLabel: 'close',
|
||||
onClick: this.handleClose,
|
||||
},
|
||||
h('i', { class: 'el-dialog__close el-icon el-icon-close' }),
|
||||
)
|
||||
: null
|
||||
const header = h(
|
||||
'div',
|
||||
const closeBtn = renderIf(this.showClose, 'button',
|
||||
{
|
||||
class: 'el-dialog__header',
|
||||
type: 'button',
|
||||
class: 'el-dialog__headerbtn',
|
||||
ariaLabel: 'close',
|
||||
onClick: this.handleClose,
|
||||
},
|
||||
[closeIcon],
|
||||
PatchFlags.PROPS,
|
||||
['onClick'],
|
||||
)
|
||||
|
||||
const header = createVNode(
|
||||
'div',
|
||||
headerKls,
|
||||
[
|
||||
$slots.header
|
||||
? $slots.header()
|
||||
: h('span', { class: 'el-dialog__title' }, this.title),
|
||||
renderSlot($slots, 'header', {}, () =>
|
||||
[createVNode('span', titleKls, toDisplayString(this.title), PatchFlags.TEXT)],
|
||||
),
|
||||
closeBtn,
|
||||
],
|
||||
)
|
||||
|
||||
const body = h(
|
||||
const body = createVNode(
|
||||
'div',
|
||||
{
|
||||
class: 'el-dialog__body',
|
||||
},
|
||||
$slots.default?.(),
|
||||
bodyKls,
|
||||
[renderSlot($slots, 'default')],
|
||||
)
|
||||
|
||||
const footer = $slots.footer
|
||||
? h('div', { class: 'el-dialog__footer' }, $slots.footer())
|
||||
: null
|
||||
const footer = renderIf(!!$slots.footer, 'div', footerKls, [renderSlot($slots, 'footer')])
|
||||
|
||||
const dialog = h(
|
||||
const dialog = createVNode(
|
||||
'div',
|
||||
{
|
||||
ariaModal: true,
|
||||
@ -167,14 +175,16 @@ export default defineComponent({
|
||||
ref: 'dialogRef',
|
||||
role: 'dialog',
|
||||
style: this.style,
|
||||
onClick: (e: MouseEvent) => e.stopPropagation(),
|
||||
onClick: stop,
|
||||
},
|
||||
[header, body, footer],
|
||||
PatchFlags.STYLE | PatchFlags.CLASS | PatchFlags.PROPS,
|
||||
['ariaLabel', 'onClick'],
|
||||
)
|
||||
|
||||
const trappedDialog = withDirectives(dialog, [[TrapFocus]])
|
||||
const overlay = withDirectives(
|
||||
h(
|
||||
createVNode(
|
||||
ElOverlay,
|
||||
{
|
||||
mask: this.modal,
|
||||
@ -182,33 +192,31 @@ export default defineComponent({
|
||||
zIndex: this.zIndex,
|
||||
},
|
||||
{
|
||||
default: () => trappedDialog,
|
||||
default: withCtx(() => [trappedDialog]),
|
||||
},
|
||||
),
|
||||
[[vShow, this.visible]],
|
||||
)
|
||||
PatchFlags.PROPS,
|
||||
['mask', 'onClick', 'zIndex'],
|
||||
), [[vShow, this.visible]])
|
||||
|
||||
const renderer = h(
|
||||
|
||||
const renderer = createVNode(
|
||||
Transition,
|
||||
{
|
||||
name: 'dialog-fade',
|
||||
onAfterEnter: this.afterEnter,
|
||||
onAfterLeave: this.afterLeave,
|
||||
'onAfter-enter': this.afterEnter,
|
||||
'onAfter-leave': this.afterLeave,
|
||||
},
|
||||
{
|
||||
default: () => overlay,
|
||||
default: () => [overlay],
|
||||
},
|
||||
PatchFlags.PROPS,
|
||||
['onAfter-enter', 'onAfter-leave'],
|
||||
)
|
||||
|
||||
if (this.appendToBody) {
|
||||
return h(
|
||||
Teleport,
|
||||
{
|
||||
to: 'body',
|
||||
},
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
return renderer
|
||||
return renderBlock(Fragment, null, [
|
||||
this.appendToBody
|
||||
? h(Teleport, { key: 0, to: 'body' }, [renderer])
|
||||
: h(Fragment, { key: 1 }, [renderer]),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
@ -1,5 +1,7 @@
|
||||
<script lang='ts'>
|
||||
import { defineComponent, h } from 'vue'
|
||||
<script lang="ts">
|
||||
import { createVNode, defineComponent, renderSlot } from 'vue'
|
||||
import { PatchFlags } from '@element-plus/utils/vnode'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElOverlay',
|
||||
props: {
|
||||
@ -22,7 +24,7 @@ export default defineComponent({
|
||||
// init here
|
||||
return () => {
|
||||
return props.mask
|
||||
? h(
|
||||
? createVNode(
|
||||
'div',
|
||||
{
|
||||
class: ['el-overlay', props.overlayClass],
|
||||
@ -31,9 +33,11 @@ export default defineComponent({
|
||||
},
|
||||
onClick: onMaskClick,
|
||||
},
|
||||
slots.default?.(),
|
||||
[renderSlot(slots, 'default')],
|
||||
PatchFlags.STYLE | PatchFlags.CLASS | PatchFlags.PROPS,
|
||||
['onClick'],
|
||||
)
|
||||
: slots.default?.()
|
||||
: renderSlot(slots, 'default')
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -51,7 +55,6 @@ export default defineComponent({
|
||||
left: 0;
|
||||
z-index: 2000;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,.5);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -1,28 +1,26 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
createVNode,
|
||||
defineComponent,
|
||||
h,
|
||||
Fragment,
|
||||
Teleport,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onActivated,
|
||||
renderSlot,
|
||||
toDisplayString,
|
||||
withCtx,
|
||||
} from 'vue'
|
||||
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
import throwError from '@element-plus/utils/error'
|
||||
import { stop } from '@element-plus/utils/dom'
|
||||
import { renderBlock, PatchFlags } from '@element-plus/utils/vnode'
|
||||
|
||||
import usePopper from './use-popper/index'
|
||||
import defaultProps from './use-popper/defaults'
|
||||
|
||||
import {
|
||||
renderMask,
|
||||
renderPopper,
|
||||
renderTrigger,
|
||||
renderArrow,
|
||||
} from './renderers'
|
||||
import { Mask, renderPopper, renderTrigger, renderArrow } from './renderers'
|
||||
|
||||
const compName = 'ElPopper'
|
||||
const UPDATE_VISIBLE_EVENT = 'update:visible'
|
||||
@ -31,9 +29,6 @@ const emits = [UPDATE_VISIBLE_EVENT, 'after-enter', 'after-leave']
|
||||
|
||||
export default defineComponent({
|
||||
name: compName,
|
||||
directives: {
|
||||
ClickOutside,
|
||||
},
|
||||
props: defaultProps,
|
||||
emits,
|
||||
setup(props, ctx) {
|
||||
@ -90,11 +85,9 @@ export default defineComponent({
|
||||
visibility,
|
||||
},
|
||||
[
|
||||
$slots.default ? h(
|
||||
Fragment,
|
||||
null,
|
||||
$slots.default(),
|
||||
) : this.content,
|
||||
renderSlot($slots, 'default', {}, () => {
|
||||
return [toDisplayString(this.content)]
|
||||
}),
|
||||
arrow,
|
||||
],
|
||||
)
|
||||
@ -109,19 +102,31 @@ export default defineComponent({
|
||||
...this.events,
|
||||
})
|
||||
|
||||
return h(Fragment, null, [
|
||||
return renderBlock(Fragment, null, [
|
||||
trigger,
|
||||
appendToBody
|
||||
? h(
|
||||
? createVNode(
|
||||
Teleport,
|
||||
{
|
||||
to: 'body',
|
||||
key: 0,
|
||||
},
|
||||
renderMask(popper, {
|
||||
hide,
|
||||
}),
|
||||
[
|
||||
createVNode(
|
||||
Mask,
|
||||
{
|
||||
hide,
|
||||
isManualMode: this.isManualMode(),
|
||||
},
|
||||
{
|
||||
default: withCtx(() => [popper]),
|
||||
},
|
||||
PatchFlags.PROPS,
|
||||
['hide', 'isManualMode'],
|
||||
),
|
||||
],
|
||||
)
|
||||
: popper,
|
||||
: renderBlock(Fragment, { key: 1 }, [popper]),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
export { default as renderMask } from './mask'
|
||||
export { default as Mask } from './mask'
|
||||
export { default as renderPopper } from './popper'
|
||||
export { default as renderTrigger } from './trigger'
|
||||
export { default as renderArrow } from './arrow'
|
||||
|
@ -1,18 +1,61 @@
|
||||
import { h, withDirectives } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { withDirectives, renderSlot, createVNode } from 'vue'
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
|
||||
interface IRenderMaskProps {
|
||||
hide: () => void
|
||||
manualMode: boolean
|
||||
}
|
||||
|
||||
export default function renderMask(popper: VNode, { hide }: IRenderMaskProps): VNode {
|
||||
return withDirectives(
|
||||
h('div', {
|
||||
class: 'el-popper__mask',
|
||||
}, popper),
|
||||
// marking excludes as any due to the current version of Vue's definition file
|
||||
// DOES NOT support types other than string as arguments
|
||||
[[ClickOutside, hide]],
|
||||
)
|
||||
const _hoist1 = {
|
||||
key: 0,
|
||||
class: 'el-popper__mask',
|
||||
}
|
||||
|
||||
const _hoist2 = {
|
||||
key: 1,
|
||||
class: 'el-popper__mask',
|
||||
}
|
||||
|
||||
// export default function renderMask(popper: VNode, { hide, manualMode }: IRenderMaskProps): VNode {
|
||||
// return manualMode ? withDirectives(
|
||||
// renderBlock('div', _hoist1, [popper]),
|
||||
// // marking excludes as any due to the current version of Vue's definition file
|
||||
// // DOES NOT support types other than string as arguments
|
||||
// [[ClickOutside, hide]],
|
||||
// ) : renderBlock('div', _hoist2, [popper])
|
||||
// }
|
||||
|
||||
export default ({
|
||||
hide,
|
||||
manualMode,
|
||||
}: IRenderMaskProps, { slots }) => {
|
||||
const children = renderSlot(slots, 'default')
|
||||
return manualMode
|
||||
? withDirectives(
|
||||
createVNode('div', _hoist1, [ children ]), [[ClickOutside, hide]],
|
||||
)
|
||||
: createVNode('div', _hoist2, [ children ])
|
||||
}
|
||||
|
||||
|
||||
// defineComponent({
|
||||
// template: `
|
||||
// <div v-if="!manualMode" v-click-outside="hide">
|
||||
// <slot />
|
||||
// </div>
|
||||
// <div v-else>
|
||||
// <slot />
|
||||
// </div>
|
||||
// `,
|
||||
// directives: {
|
||||
// ClickOutside,
|
||||
// },
|
||||
// props: {
|
||||
// hide: {
|
||||
// type: Function as PropType<() => void>,
|
||||
// },
|
||||
// manualMode: {
|
||||
// type: Boolean,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
@ -263,6 +263,7 @@ export default function (props: IPopperOptions, { emit }: SetupContext<string[]>
|
||||
emit('after-leave')
|
||||
},
|
||||
initializePopper,
|
||||
isManualMode,
|
||||
arrowRef,
|
||||
events,
|
||||
popperId,
|
||||
|
@ -13,14 +13,16 @@ Dialog pops up a dialog box, and it's quite customizable.
|
||||
|
||||
<el-dialog
|
||||
title="Tips"
|
||||
:visible.sync="dialogVisible"
|
||||
v-model="dialogVisible"
|
||||
width="30%"
|
||||
:before-close="handleClose">
|
||||
<span>This is a message</span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="dialogVisible = false">Confirm</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<script>
|
||||
@ -35,6 +37,7 @@ Dialog pops up a dialog box, and it's quite customizable.
|
||||
this.$confirm('Are you sure to close this dialog?')
|
||||
.then(_ => {
|
||||
done();
|
||||
this.dialogVisible = false
|
||||
})
|
||||
.catch(_ => {});
|
||||
}
|
||||
@ -58,7 +61,7 @@ The content of Dialog can be anything, even a table or a form. This example show
|
||||
<!-- Table -->
|
||||
<el-button type="text" @click="dialogTableVisible = true">open a Table nested Dialog</el-button>
|
||||
|
||||
<el-dialog title="Shipping address" :visible.sync="dialogTableVisible">
|
||||
<el-dialog title="Shipping address" v-model="dialogTableVisible">
|
||||
<el-table :data="gridData">
|
||||
<el-table-column property="date" label="Date" width="150"></el-table-column>
|
||||
<el-table-column property="name" label="Name" width="200"></el-table-column>
|
||||
@ -69,7 +72,7 @@ The content of Dialog can be anything, even a table or a form. This example show
|
||||
<!-- Form -->
|
||||
<el-button type="text" @click="dialogFormVisible = true">open a Form nested Dialog</el-button>
|
||||
|
||||
<el-dialog title="Shipping address" :visible.sync="dialogFormVisible">
|
||||
<el-dialog title="Shipping address" v-model="dialogFormVisible">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="Promotion name" :label-width="formLabelWidth">
|
||||
<el-input v-model="form.name" autocomplete="off"></el-input>
|
||||
@ -135,17 +138,21 @@ If a Dialog is nested in another Dialog, `append-to-body` is required.
|
||||
<template>
|
||||
<el-button type="text" @click="outerVisible = true">open the outer Dialog</el-button>
|
||||
|
||||
<el-dialog title="Outer Dialog" :visible.sync="outerVisible">
|
||||
<el-dialog
|
||||
width="30%"
|
||||
title="Inner Dialog"
|
||||
:visible.sync="innerVisible"
|
||||
append-to-body>
|
||||
</el-dialog>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-dialog title="Outer Dialog" v-model="outerVisible">
|
||||
<template #default>
|
||||
<el-dialog
|
||||
width="30%"
|
||||
title="Inner Dialog"
|
||||
v-model="innerVisible"
|
||||
append-to-body>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="outerVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="innerVisible = true">open the inner Dialog</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
@ -172,14 +179,16 @@ Dialog's content can be centered.
|
||||
|
||||
<el-dialog
|
||||
title="Warning"
|
||||
:visible.sync="centerDialogVisible"
|
||||
v-model="centerDialogVisible"
|
||||
width="30%"
|
||||
center>
|
||||
<span>It should be noted that the content will not be aligned in center by default</span>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="centerDialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="centerDialogVisible = false">Confirm</el-button>
|
||||
</span>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="centerDialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" @click="centerDialogVisible = false">Confirm</el-button>
|
||||
</span>
|
||||
</footer>
|
||||
</el-dialog>
|
||||
|
||||
<script>
|
||||
|
Loading…
Reference in New Issue
Block a user