fix(popper): addressing comments

This commit is contained in:
JeremyWuuuuu 2020-09-15 21:58:36 +08:00 committed by hangzou
parent 9902c33dbf
commit 37f5b6164b
9 changed files with 186 additions and 99 deletions

View File

@ -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: {

View File

@ -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;
}

View File

@ -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, ''))
}

View File

@ -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]],
)
}

View File

@ -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]],
),
},
)
}

View File

@ -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)
}

View File

@ -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)
})

View File

@ -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)
}

View File

@ -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">