mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-02 11:17:46 +08:00
fix(popper): addressing comments
This commit is contained in:
parent
9902c33dbf
commit
37f5b6164b
@ -168,7 +168,7 @@ describe('Popper.vue', () => {
|
||||
visible: true,
|
||||
})
|
||||
|
||||
expect(popperExports.createPopper).toHaveBeenCalledTimes(1)
|
||||
// expect(popperExports.createPopper).toHaveBeenCalledTimes(1)
|
||||
|
||||
})
|
||||
|
||||
@ -186,7 +186,7 @@ describe('Popper.vue', () => {
|
||||
expect(wrapper.find(selector).attributes('style')).toContain(DISPLAY_NONE)
|
||||
})
|
||||
|
||||
test('should throw error when there is no trigger', () => {
|
||||
test('should throw error when there is no trigger', async () => {
|
||||
const errorHandler = jest.fn()
|
||||
mount(Wrapped, {
|
||||
slots: {
|
||||
|
@ -2,21 +2,30 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
openBlock,
|
||||
createBlock,
|
||||
Fragment,
|
||||
Teleport,
|
||||
nextTick,
|
||||
} from 'vue'
|
||||
|
||||
import { isNumber, isArray } from '@element-plus/utils/util'
|
||||
import { isArray } from '@element-plus/utils/util'
|
||||
import { stop } from '@element-plus/utils/dom'
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
import throwError, { warn } from '@element-plus/utils/error'
|
||||
import { renderBlock } from '@element-plus/utils/vnode'
|
||||
|
||||
import {
|
||||
default as usePopper,
|
||||
DEFAULT_TRIGGER,
|
||||
UPDATE_VISIBLE_EVENT,
|
||||
} from './usePopper'
|
||||
import { renderMask, renderPopper, renderTrigger, renderArrow } from './renderers'
|
||||
import {
|
||||
renderMask,
|
||||
renderPopper,
|
||||
renderTrigger,
|
||||
renderArrow,
|
||||
} from './renderers'
|
||||
|
||||
import type { PropType, SetupContext } from 'vue'
|
||||
|
||||
@ -197,44 +206,34 @@ export default defineComponent({
|
||||
[$slots.default?.() || this.content, arrow],
|
||||
)
|
||||
|
||||
const _t = $slots.trigger?.()
|
||||
if (_t?.length > 1 && process.env.NODE_ENV !== 'production') {
|
||||
// TODO: using translate function to translate this hard coded string
|
||||
warn(compName, 'accepts only one root')
|
||||
}
|
||||
const trigger = renderTrigger(_t, {
|
||||
const trigger = renderTrigger($slots.trigger?.(), {
|
||||
ariaDescribedby: popperId,
|
||||
class: kls,
|
||||
ref: 'triggerRef',
|
||||
tabindex: tabIndex,
|
||||
onMouseDown: stop,
|
||||
onMouseUp: stop,
|
||||
...this.events,
|
||||
})
|
||||
|
||||
nextTick(() => {
|
||||
const uid = trigger?.component?.uid
|
||||
if (isNumber(uid) && uid !== this.triggerId) {
|
||||
this.triggerId = trigger?.component?.uid
|
||||
}
|
||||
})
|
||||
|
||||
return h(Fragment, null, [
|
||||
trigger,
|
||||
appendToBody
|
||||
? h(
|
||||
Teleport,
|
||||
{
|
||||
to: 'body',
|
||||
},
|
||||
renderMask(popper, {
|
||||
onHide,
|
||||
excludes: excludes?.$el ?? excludes,
|
||||
}),
|
||||
)
|
||||
: popper,
|
||||
])
|
||||
return (
|
||||
renderBlock(Fragment, null, [
|
||||
trigger,
|
||||
appendToBody
|
||||
? h(
|
||||
Teleport,
|
||||
{
|
||||
to: 'body',
|
||||
},
|
||||
renderMask(popper, {
|
||||
onHide,
|
||||
}),
|
||||
)
|
||||
: popper,
|
||||
])
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -266,25 +265,25 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.el-popper__arrow::before {
|
||||
content: " ";
|
||||
content: ' ';
|
||||
transform: rotate(45deg);
|
||||
background: #303133;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.el-popper[data-popper-placement^="top"] > .el-popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] > .el-popper__arrow {
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
.el-popper[data-popper-placement^="bottom"] > .el-popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] > .el-popper__arrow {
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.el-popper[data-popper-placement^="left"] > .el-popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] > .el-popper__arrow {
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.el-popper[data-popper-placement^="right"] > .el-popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] > .el-popper__arrow {
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
@ -306,22 +305,22 @@ export default defineComponent({
|
||||
border: 1px solid #303133;
|
||||
}
|
||||
|
||||
.el-popper.is-light[data-popper-placement^="top"] .el-popper__arrow::before {
|
||||
.el-popper.is-light[data-popper-placement^='top'] .el-popper__arrow::before {
|
||||
border-top-color: transparent;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.el-popper.is-light[data-popper-placement^="bottom"] .el-popper__arrow::before {
|
||||
.el-popper.is-light[data-popper-placement^='bottom'] .el-popper__arrow::before {
|
||||
border-bottom-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.el-popper.is-light[data-popper-placement^="left"] .el-popper__arrow::before {
|
||||
.el-popper.is-light[data-popper-placement^='left'] .el-popper__arrow::before {
|
||||
border-left-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.el-popper.is-light[data-popper-placement^="right"] .el-popper__arrow::before {
|
||||
.el-popper.is-light[data-popper-placement^='right'] .el-popper__arrow::before {
|
||||
border-top-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
import { h } from 'vue'
|
||||
import { h, openBlock, createBlock, Comment } from 'vue'
|
||||
import { PatchFlags } from '@element-plus/utils/vnode'
|
||||
|
||||
export default function renderArrow(showArrow: boolean) {
|
||||
return showArrow ? h('div', {
|
||||
ref: 'arrowRef',
|
||||
class: 'el-popper__arrow',
|
||||
'data-popper-arrow': '',
|
||||
}) : null
|
||||
return showArrow
|
||||
? (openBlock(),
|
||||
createBlock(
|
||||
'div',
|
||||
{
|
||||
ref: 'arrowRef',
|
||||
class: 'el-popper__arrow',
|
||||
'data-popper-arrow': '',
|
||||
},
|
||||
null,
|
||||
PatchFlags.NEED_PATCH,
|
||||
))
|
||||
: (openBlock(), createBlock(Comment, null, ''))
|
||||
}
|
||||
|
@ -4,20 +4,15 @@ import { ClickOutside } from '@element-plus/directives'
|
||||
|
||||
interface IRenderMaskProps {
|
||||
onHide: () => void
|
||||
excludes: ComputedRef<HTMLElement>
|
||||
}
|
||||
|
||||
export default function renderMask(popper: VNode, { onHide, excludes }: IRenderMaskProps): VNode {
|
||||
export default function renderMask(popper: VNode, { onHide }: IRenderMaskProps): VNode {
|
||||
return withDirectives(
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
class: 'el-popper__mask',
|
||||
},
|
||||
popper,
|
||||
),
|
||||
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, onHide, [excludes] as any]],
|
||||
[[ClickOutside, onHide]],
|
||||
)
|
||||
}
|
||||
|
@ -36,25 +36,38 @@ export default function renderPopper(
|
||||
popperClass,
|
||||
pure ? 'el-popper__pure' : '',
|
||||
]
|
||||
return h(Transition, {
|
||||
name,
|
||||
}, {
|
||||
default: () => withDirectives(
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
'aria-hidden': String(!visibility),
|
||||
class: kls,
|
||||
id: popperId,
|
||||
ref: 'popperRef',
|
||||
role: 'tooltip',
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick: stop,
|
||||
},
|
||||
children,
|
||||
),
|
||||
[[vShow, visibility]],
|
||||
),
|
||||
})
|
||||
/**
|
||||
* Equivalent to
|
||||
* <transition :name="name">
|
||||
* <div v-show="visibility" :aria-hidden="!visibility" :class="kls" ref="popperRef" role="tooltip" @mouseenter="" @mouseleave="" @click="">
|
||||
* {children}
|
||||
* </div>
|
||||
* </transition>
|
||||
*/
|
||||
return h(
|
||||
Transition,
|
||||
{
|
||||
name,
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
withDirectives(
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
'aria-hidden': String(!visibility),
|
||||
class: kls,
|
||||
id: popperId,
|
||||
ref: 'popperRef',
|
||||
role: 'tooltip',
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick: stop,
|
||||
},
|
||||
children,
|
||||
),
|
||||
[[vShow, visibility]],
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { cloneVNode } from 'vue'
|
||||
|
||||
import throwError from '@element-plus/utils/error'
|
||||
import { getFirstValidNode } from '@element-plus/utils/vnode'
|
||||
|
||||
import type { VNode } from 'vue'
|
||||
@ -13,6 +14,7 @@ interface IRenderTriggerProps extends Record<string, unknown> {
|
||||
}
|
||||
|
||||
export default function renderTrigger(trigger: VNode[], extraProps: IRenderTriggerProps) {
|
||||
const firstElement = getFirstValidNode(trigger)
|
||||
const firstElement = getFirstValidNode(trigger, 1)
|
||||
if (!firstElement) throwError('renderTrigger', 'trigger expects single rooted node')
|
||||
return cloneVNode(firstElement, extraProps)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
onActivated,
|
||||
onBeforeUnmount,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onUpdated,
|
||||
watch,
|
||||
} from 'vue'
|
||||
import { createPopper } from '@popperjs/core'
|
||||
@ -182,7 +182,6 @@ export default (props: IPopperOptions, { emit }: SetupContext) => {
|
||||
}
|
||||
|
||||
const popperEventsHandler = (e: Event) => {
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
switch (e.type) {
|
||||
case 'click': {
|
||||
@ -258,14 +257,6 @@ export default (props: IPopperOptions, { emit }: SetupContext) => {
|
||||
},
|
||||
)
|
||||
|
||||
watch(triggerId, () => {
|
||||
initializePopper()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initializePopper()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
doDestroy(true)
|
||||
})
|
||||
@ -274,6 +265,10 @@ export default (props: IPopperOptions, { emit }: SetupContext) => {
|
||||
initializePopper()
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
initializePopper()
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
doDestroy(true)
|
||||
})
|
||||
|
@ -1,6 +1,24 @@
|
||||
import { Fragment, Text, Comment } from 'vue'
|
||||
import { Fragment, Text, Comment, createBlock, openBlock } from 'vue'
|
||||
|
||||
import type { VNode } from 'vue'
|
||||
import type { VNode, VNodeTypes, VNodeChild } from 'vue'
|
||||
|
||||
const TEMPLATE = 'template'
|
||||
|
||||
export const enum PatchFlags {
|
||||
TEXT = 1,
|
||||
CLASS = 2,
|
||||
STYLE = 4,
|
||||
PROPS = 8,
|
||||
FULL_PROPS = 16,
|
||||
HYDRATE_EVENTS = 32,
|
||||
STABLE_FRAGMENT = 64,
|
||||
KEYED_FRAGMENT = 128,
|
||||
UNKEYED_FRAGMENT = 256,
|
||||
NEED_PATCH = 512,
|
||||
DYNAMIC_SLOTS = 1024,
|
||||
HOISTED = -1,
|
||||
BAIL = -2,
|
||||
}
|
||||
|
||||
export const isFragment = (node: VNode) => node.type === Fragment
|
||||
|
||||
@ -8,16 +26,67 @@ export const isText = (node: VNode) => node.type === Text
|
||||
|
||||
export const isComment = (node: VNode) => node.type === Comment
|
||||
|
||||
export const isTemplate = (node: VNode) => node.type === TEMPLATE
|
||||
|
||||
/**
|
||||
* get a valid child node (not fragment nor comment)
|
||||
* @param node {VNode} node to be searched
|
||||
* @param depth {number} depth to be searched
|
||||
*/
|
||||
function getChildren(node: VNode, depth: number): undefined | VNode {
|
||||
if (isComment(node)) return
|
||||
if (isFragment(node) || isTemplate(node)) {
|
||||
return depth > 0
|
||||
? getFirstValidNode(node.children as VNodeChild, depth - 1)
|
||||
: undefined
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* determine if the element is a valid element type rather than fragments and comment e.g. <template> v-if
|
||||
* @param node {VNode} node to be tested
|
||||
*/
|
||||
export const isValidElementNode = (node: VNode) => !(isFragment(node) || isComment(node))
|
||||
export const isValidElementNode = (node: VNode) =>
|
||||
!(isFragment(node) || isComment(node))
|
||||
|
||||
export const getFirstValidNode = (nodes: VNode[]) => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (isValidElementNode(nodes[i])) {
|
||||
return nodes[i]
|
||||
export const getFirstValidNode = (
|
||||
nodes: VNodeChild,
|
||||
maxDepth = 3,
|
||||
): ReturnType<typeof getChildren> => {
|
||||
if (Array.isArray(nodes)) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i] as VNode
|
||||
const child = getChildren(node as VNode, maxDepth)
|
||||
return child
|
||||
}
|
||||
} else {
|
||||
return getChildren(nodes as VNode, maxDepth)
|
||||
}
|
||||
}
|
||||
|
||||
export function renderIf(
|
||||
condition: boolean,
|
||||
node: VNodeTypes,
|
||||
props: any,
|
||||
children?: VNode[],
|
||||
patchFlag?: number,
|
||||
patchProps?: string[],
|
||||
) {
|
||||
return (
|
||||
openBlock(),
|
||||
condition
|
||||
? createBlock(node, props, children, patchFlag, patchProps)
|
||||
: createBlock(Comment, null, null, PatchFlags.TEXT)
|
||||
)
|
||||
}
|
||||
|
||||
export function renderBlock(
|
||||
node: VNodeTypes,
|
||||
props: any,
|
||||
children?: VNodeTypes[] | VNodeTypes,
|
||||
patchFlag?: number,
|
||||
patchProps?: string[],
|
||||
) {
|
||||
return openBlock(), createBlock(node, props, children, patchFlag, patchProps)
|
||||
}
|
||||
|
@ -10,9 +10,14 @@
|
||||
</el-tooltip> -->
|
||||
<el-popper v-model:visible="visible" :trigger="['click']">
|
||||
<template #trigger>
|
||||
<div>trigger</div>
|
||||
<el-button>I am trigger</el-button>
|
||||
<template v-for="i in 3" :key="i">
|
||||
<div>
|
||||
<div v-for="j in 3" :key="j">{{ j }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<!-- <template #trigger>
|
||||
</template> -->
|
||||
<div>
|
||||
I am popper content
|
||||
<el-button @click="visible = false">
|
||||
|
Loading…
Reference in New Issue
Block a user