[完善]使用teleport重构tooltip所使用的内部popper组件,支持移动到tooltip内容而不隐藏tooltip

This commit is contained in:
xumi 2021-12-30 02:10:28 +08:00
parent 1ab133eb0b
commit 8709cb73a4
4 changed files with 109 additions and 111 deletions

View File

@ -14,21 +14,21 @@
@attr : ~'[position=@{position}]';
&{
border: 1px solid @m-border-color;
&.layui-popper@{attr}{
margin-@{contrary_position}: 6px;
.layui-popper-arrow {
@{contrary_position}: -6px;
border-@{contrary_position}-width: 0;
border-@{position}-color: @m-border-color;
&::after{
@{contrary_position}: 1px;
&@{attr}{
margin-@{contrary_position}: 6px;
.layui-popper-arrow {
@{contrary_position}: -6px;
border-@{contrary_position}-width: 0;
margin-@{margin_postion}: -6px;
border-@{position}-color: @color;
border-@{position}-color: @m-border-color;
&::after{
@{contrary_position}: 1px;
border-@{contrary_position}-width: 0;
margin-@{margin_postion}: -6px;
border-@{position}-color: @color;
}
}
}
}
}
}
// 统一设置四个方向的主题
.theme(@background-color, @color, @border-color) {
@ -43,7 +43,7 @@
// 箭头默认居中
.arrow-default-center(@position, @prop) {
@attr : ~'[position=@{position}]';
&.layui-popper@{attr} {
&@{attr} {
.layui-popper-arrow{
@{prop}: -moz-calc(50% - 6px);
@{prop}: -webkit-calc(50% - 6px);
@ -58,6 +58,23 @@
.arrow-default-center(right, top);
}
// 填充popper支持可以移动到popper使用到
.single-fill-popper(@position, @contrary_position, @zeroPosition, @all, @seven){
@attr : ~'[position=@{position}]';
&@{attr}::after{
@{contrary_position}: -7px;
@{zeroPosition}: 0;
@{all}: 100%;
@{seven}: 7px;
}
}
.fill-popper(){
.single-fill-popper(top, bottom, left, width, height);
.single-fill-popper(bottom, top, left, width, height);
.single-fill-popper(left, right, bottom, height, width);
.single-fill-popper(right, left, bottom, height, width);
}
// 样式开始
.layui-popper {
position: fixed;
@ -76,9 +93,17 @@
// 箭头默认居中
.all-arrow-default-center();
&::after{
content: " ";
position: absolute;
display: block;
}
// 填充
.fill-popper();
.layui-popper-arrow {
&,&::after{
position: absolute;
position: absolute;
display: block;
width: 0;
height: 0;

View File

@ -1,15 +1,17 @@
<template>
<transition v-show="innerVisible">
<div
ref="popper"
:class="['layui-popper', { 'layui-dark': innnerIsDark }]"
:style="style"
:position="innnerPosition"
>
<slot>{{ content.value }}</slot>
<div class="layui-popper-arrow"></div>
</div>
</transition>
<teleport to="body" v-if="isExist">
<transition v-show="innerVisible">
<div
ref="popper"
:class="['layui-popper', { 'layui-dark': isDark }]"
:style="style"
:position="position"
>
<slot>{{ content }}</slot>
<div class="layui-popper-arrow"></div>
</div>
</transition>
</teleport>
</template>
<script lang="ts">
const NAME = "LayPopper";
@ -25,30 +27,33 @@ import {
CSSProperties,
ref,
watch,
onUpdated,
defineEmits,
onMounted,
Ref,
} from "vue";
import { on } from "../../tools/domUtil";
const props = withDefaults(
defineProps<{
el: any;
content?: Ref<string | Number>;
position?: Ref<string>;
content?: string | Number;
position?: string;
trigger?: string;
enterable?: boolean;
isDark?: Ref<boolean>;
disabled?: Ref<boolean>;
visible?: Ref<boolean>;
isCanHide?: Ref<boolean>;
updateVisible?: Function;
isDark?: boolean;
disabled?: boolean;
visible?: boolean;
isCanHide?: boolean;
}>(),
{
position: 'top',
isDark: true,
disabled: false,
enterable: true,
visible: true,
isCanHide: true,
trigger: "hover",
}
);
const emit = defineEmits(["update:visible"]);
const EVENT_MAP: any = {
hover: ["mouseenter", null, "mouseleave", false],
@ -63,55 +68,52 @@ if (!triggerArr) {
const style = ref<CSSProperties>({ top: -window.innerHeight + "px", left: 0 });
const checkTarget = ref(false);
const popper = ref<HTMLDivElement>({} as HTMLDivElement);
const tempPosition = props.position ?? ref("top");
const innnerPosition = ref(tempPosition.value);
const innnerIsDark = ref(props.isDark ?? true);
const innnerDisabled = ref(props.disabled ?? false);
const innnerPosition = ref(props.position);
const innerVisible = ref(props.visible ?? true);
const isExist = ref(false);
watch(innerVisible, (val) => {
invokeShowPosistion();
props.updateVisible && props.updateVisible(val);
emit("update:visible", val);
});
watch(innnerDisabled, (val) => {
innerVisible.value = false;
watch(popper, (val) => {
if (props.trigger === 'hover' && props.enterable) {
on(popper.value, EVENT_MAP['hover'][0], doShow);
on(popper.value, EVENT_MAP['hover'][2], doHidden);
}
});
watch(
() => props.content?.value,
() => props.content,
(val) => {
innerVisible.value && invokeShowPosistion();
}
);
const doShow = function () {
if (!innnerDisabled.value) {
if (!props.disabled) {
innerVisible.value = true;
isExist.value = true;
}
};
const doHidden = function (e: MouseEvent) {
// ||(props.enterable && popper.value.contains(e.target as Node))
if (
(checkTarget.value && props.el.contains(e.target)) ||
(props.enterable && popper.value.contains(e.target as Node))
(checkTarget.value && props.el.contains(e.target))
)
return;
// style.value = {top: (-window.innerHeight) + 'px',left:0};
// popper.value.remove();
if (props.isCanHide?.value !== false) {
if (props.isCanHide !== false) {
innerVisible.value = false;
}
innnerPosition.value = tempPosition.value;
innnerPosition.value = props.position;
};
//
on(props.el, triggerArr[0], doShow);
on(triggerArr[1] ?? props.el, triggerArr[2], doHidden);
checkTarget.value = triggerArr[3];
//
const showPosistion = function () {
postionFns[tempPosition.value] &&
(style.value = postionFns[tempPosition.value](
postionFns[props.position] &&
(style.value = postionFns[props.position](
props.el,
popper.value,
innnerPosition
@ -126,6 +128,12 @@ const invokeShowPosistion = function () {
setTimeout(() => innerVisible.value && showPosistion(), 2);
}
};
//
on(props.el, triggerArr[0], doShow);
on(triggerArr[1] ?? props.el, triggerArr[2], doHidden);
checkTarget.value = triggerArr[3];
onMounted(() => {
invokeShowPosistion();
});

View File

@ -1,47 +0,0 @@
import { h, ref, render, watchEffect, watch } from "vue";
import popper from "./index.vue";
import { once } from "../../tools/domUtil";
const EVENT_MAP: any = {
hover: "mouseenter",
click: "click",
};
const usePopper = {
createPopper(el: HTMLElement, props: any, trigger: string) {
const _this = this;
once(el, EVENT_MAP[trigger], () => {
// TODO 临时解决方案
const _props: any = { el };
for (const key in props) {
_props[key] = ref(props[key]);
}
_props.updateVisible = function (val: boolean) {
_props.visible && (_props.visible.value = val);
};
_this.renderPopper(_props);
watchEffect(() => {
for (const key in _props) {
if (key === "visible") {
continue;
}
_props[key].value = props[key];
}
});
watch(
() => props.visible,
(val: boolean) => {
_props.updateVisible(val);
}
);
});
},
renderPopper(props: any) {
const container: HTMLDivElement = document.createElement("div");
// container.setAttribute("class", "lay-div");
const node = h(popper, props);
render(node, container);
container.firstElementChild &&
document.body.appendChild(container.firstElementChild);
return node;
},
};
export default usePopper;

View File

@ -1,8 +1,15 @@
<template>
<slot></slot>
<lay-popper v-if="isMounted" v-bind="innerProps"></lay-popper>
</template>
<script lang="ts">
import usePopper from "../popper/usePopper";
import { defineComponent } from "vue";
import LayPopper from "../popper/index.vue";
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "LayTooltip",
components: {
LayPopper
},
props: {
content: {
type: [Number, String],
@ -22,21 +29,26 @@ export default defineComponent({
},
visible: {
type: Boolean,
default: true,
default: false,
},
isCanHide: {
type: Boolean,
default: true,
},
},
setup(){
const isMounted = ref(false);
return {
isMounted
}
},
computed: {
innerProps(){
return {el: this.$el.nextElementSibling, ...this.$props};
}
},
mounted() {
const _this = this;
this.$nextTick(() => {
usePopper.createPopper(_this.$el, _this.$props, "hover");
});
},
render() {
return this.$slots.default && this.$slots.default()[0];
},
this.$nextTick(() => this.isMounted = true);
}
});
</script>