✨[完善]使用teleport重构tooltip所使用的内部popper组件,支持移动到tooltip内容而不隐藏tooltip
This commit is contained in:
parent
1ab133eb0b
commit
8709cb73a4
@ -14,21 +14,21 @@
|
|||||||
@attr : ~'[position=@{position}]';
|
@attr : ~'[position=@{position}]';
|
||||||
&{
|
&{
|
||||||
border: 1px solid @m-border-color;
|
border: 1px solid @m-border-color;
|
||||||
&.layui-popper@{attr}{
|
&@{attr}{
|
||||||
margin-@{contrary_position}: 6px;
|
margin-@{contrary_position}: 6px;
|
||||||
.layui-popper-arrow {
|
.layui-popper-arrow {
|
||||||
@{contrary_position}: -6px;
|
@{contrary_position}: -6px;
|
||||||
border-@{contrary_position}-width: 0;
|
|
||||||
border-@{position}-color: @m-border-color;
|
|
||||||
&::after{
|
|
||||||
@{contrary_position}: 1px;
|
|
||||||
border-@{contrary_position}-width: 0;
|
border-@{contrary_position}-width: 0;
|
||||||
margin-@{margin_postion}: -6px;
|
border-@{position}-color: @m-border-color;
|
||||||
border-@{position}-color: @color;
|
&::after{
|
||||||
|
@{contrary_position}: 1px;
|
||||||
|
border-@{contrary_position}-width: 0;
|
||||||
|
margin-@{margin_postion}: -6px;
|
||||||
|
border-@{position}-color: @color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 统一设置四个方向的主题
|
// 统一设置四个方向的主题
|
||||||
.theme(@background-color, @color, @border-color) {
|
.theme(@background-color, @color, @border-color) {
|
||||||
@ -43,7 +43,7 @@
|
|||||||
// 箭头默认居中
|
// 箭头默认居中
|
||||||
.arrow-default-center(@position, @prop) {
|
.arrow-default-center(@position, @prop) {
|
||||||
@attr : ~'[position=@{position}]';
|
@attr : ~'[position=@{position}]';
|
||||||
&.layui-popper@{attr} {
|
&@{attr} {
|
||||||
.layui-popper-arrow{
|
.layui-popper-arrow{
|
||||||
@{prop}: -moz-calc(50% - 6px);
|
@{prop}: -moz-calc(50% - 6px);
|
||||||
@{prop}: -webkit-calc(50% - 6px);
|
@{prop}: -webkit-calc(50% - 6px);
|
||||||
@ -58,6 +58,23 @@
|
|||||||
.arrow-default-center(right, top);
|
.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 {
|
.layui-popper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -76,9 +93,17 @@
|
|||||||
// 箭头默认居中
|
// 箭头默认居中
|
||||||
.all-arrow-default-center();
|
.all-arrow-default-center();
|
||||||
|
|
||||||
|
&::after{
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
// 填充
|
||||||
|
.fill-popper();
|
||||||
|
|
||||||
.layui-popper-arrow {
|
.layui-popper-arrow {
|
||||||
&,&::after{
|
&,&::after{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition v-show="innerVisible">
|
<teleport to="body" v-if="isExist">
|
||||||
<div
|
<transition v-show="innerVisible">
|
||||||
ref="popper"
|
<div
|
||||||
:class="['layui-popper', { 'layui-dark': innnerIsDark }]"
|
ref="popper"
|
||||||
:style="style"
|
:class="['layui-popper', { 'layui-dark': isDark }]"
|
||||||
:position="innnerPosition"
|
:style="style"
|
||||||
>
|
:position="position"
|
||||||
<slot>{{ content.value }}</slot>
|
>
|
||||||
<div class="layui-popper-arrow"></div>
|
<slot>{{ content }}</slot>
|
||||||
</div>
|
<div class="layui-popper-arrow"></div>
|
||||||
</transition>
|
</div>
|
||||||
|
</transition>
|
||||||
|
</teleport>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
const NAME = "LayPopper";
|
const NAME = "LayPopper";
|
||||||
@ -25,30 +27,33 @@ import {
|
|||||||
CSSProperties,
|
CSSProperties,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
onUpdated,
|
|
||||||
defineEmits,
|
defineEmits,
|
||||||
onMounted,
|
onMounted,
|
||||||
Ref,
|
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { on } from "../../tools/domUtil";
|
import { on } from "../../tools/domUtil";
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
el: any;
|
el: any;
|
||||||
content?: Ref<string | Number>;
|
content?: string | Number;
|
||||||
position?: Ref<string>;
|
position?: string;
|
||||||
trigger?: string;
|
trigger?: string;
|
||||||
enterable?: boolean;
|
enterable?: boolean;
|
||||||
isDark?: Ref<boolean>;
|
isDark?: boolean;
|
||||||
disabled?: Ref<boolean>;
|
disabled?: boolean;
|
||||||
visible?: Ref<boolean>;
|
visible?: boolean;
|
||||||
isCanHide?: Ref<boolean>;
|
isCanHide?: boolean;
|
||||||
updateVisible?: Function;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
position: 'top',
|
||||||
|
isDark: true,
|
||||||
|
disabled: false,
|
||||||
enterable: true,
|
enterable: true,
|
||||||
|
visible: true,
|
||||||
|
isCanHide: true,
|
||||||
trigger: "hover",
|
trigger: "hover",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const emit = defineEmits(["update:visible"]);
|
||||||
|
|
||||||
const EVENT_MAP: any = {
|
const EVENT_MAP: any = {
|
||||||
hover: ["mouseenter", null, "mouseleave", false],
|
hover: ["mouseenter", null, "mouseleave", false],
|
||||||
@ -63,55 +68,52 @@ if (!triggerArr) {
|
|||||||
const style = ref<CSSProperties>({ top: -window.innerHeight + "px", left: 0 });
|
const style = ref<CSSProperties>({ top: -window.innerHeight + "px", left: 0 });
|
||||||
const checkTarget = ref(false);
|
const checkTarget = ref(false);
|
||||||
const popper = ref<HTMLDivElement>({} as HTMLDivElement);
|
const popper = ref<HTMLDivElement>({} as HTMLDivElement);
|
||||||
const tempPosition = props.position ?? ref("top");
|
const innnerPosition = ref(props.position);
|
||||||
const innnerPosition = ref(tempPosition.value);
|
|
||||||
const innnerIsDark = ref(props.isDark ?? true);
|
|
||||||
const innnerDisabled = ref(props.disabled ?? false);
|
|
||||||
const innerVisible = ref(props.visible ?? true);
|
const innerVisible = ref(props.visible ?? true);
|
||||||
|
const isExist = ref(false);
|
||||||
|
|
||||||
watch(innerVisible, (val) => {
|
watch(innerVisible, (val) => {
|
||||||
invokeShowPosistion();
|
invokeShowPosistion();
|
||||||
props.updateVisible && props.updateVisible(val);
|
emit("update:visible", val);
|
||||||
});
|
});
|
||||||
watch(innnerDisabled, (val) => {
|
watch(popper, (val) => {
|
||||||
innerVisible.value = false;
|
if (props.trigger === 'hover' && props.enterable) {
|
||||||
|
on(popper.value, EVENT_MAP['hover'][0], doShow);
|
||||||
|
on(popper.value, EVENT_MAP['hover'][2], doHidden);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
watch(
|
watch(
|
||||||
() => props.content?.value,
|
() => props.content,
|
||||||
(val) => {
|
(val) => {
|
||||||
innerVisible.value && invokeShowPosistion();
|
innerVisible.value && invokeShowPosistion();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const doShow = function () {
|
const doShow = function () {
|
||||||
if (!innnerDisabled.value) {
|
if (!props.disabled) {
|
||||||
innerVisible.value = true;
|
innerVisible.value = true;
|
||||||
|
isExist.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const doHidden = function (e: MouseEvent) {
|
const doHidden = function (e: MouseEvent) {
|
||||||
|
// ||(props.enterable && popper.value.contains(e.target as Node))
|
||||||
if (
|
if (
|
||||||
(checkTarget.value && props.el.contains(e.target)) ||
|
(checkTarget.value && props.el.contains(e.target))
|
||||||
(props.enterable && popper.value.contains(e.target as Node))
|
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
// style.value = {top: (-window.innerHeight) + 'px',left:0};
|
// style.value = {top: (-window.innerHeight) + 'px',left:0};
|
||||||
// popper.value.remove();
|
// popper.value.remove();
|
||||||
if (props.isCanHide?.value !== false) {
|
if (props.isCanHide !== false) {
|
||||||
innerVisible.value = 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 () {
|
const showPosistion = function () {
|
||||||
postionFns[tempPosition.value] &&
|
postionFns[props.position] &&
|
||||||
(style.value = postionFns[tempPosition.value](
|
(style.value = postionFns[props.position](
|
||||||
props.el,
|
props.el,
|
||||||
popper.value,
|
popper.value,
|
||||||
innnerPosition
|
innnerPosition
|
||||||
@ -126,6 +128,12 @@ const invokeShowPosistion = function () {
|
|||||||
setTimeout(() => innerVisible.value && showPosistion(), 2);
|
setTimeout(() => innerVisible.value && showPosistion(), 2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 事件绑定
|
||||||
|
on(props.el, triggerArr[0], doShow);
|
||||||
|
on(triggerArr[1] ?? props.el, triggerArr[2], doHidden);
|
||||||
|
checkTarget.value = triggerArr[3];
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
invokeShowPosistion();
|
invokeShowPosistion();
|
||||||
});
|
});
|
||||||
|
@ -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;
|
|
@ -1,8 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<slot></slot>
|
||||||
|
<lay-popper v-if="isMounted" v-bind="innerProps"></lay-popper>
|
||||||
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import usePopper from "../popper/usePopper";
|
import LayPopper from "../popper/index.vue";
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, ref } from "vue";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LayTooltip",
|
name: "LayTooltip",
|
||||||
|
components: {
|
||||||
|
LayPopper
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
content: {
|
content: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
@ -22,21 +29,26 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: false,
|
||||||
},
|
},
|
||||||
isCanHide: {
|
isCanHide: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup(){
|
||||||
|
const isMounted = ref(false);
|
||||||
|
return {
|
||||||
|
isMounted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
innerProps(){
|
||||||
|
return {el: this.$el.nextElementSibling, ...this.$props};
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const _this = this;
|
this.$nextTick(() => this.isMounted = true);
|
||||||
this.$nextTick(() => {
|
}
|
||||||
usePopper.createPopper(_this.$el, _this.$props, "hover");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default && this.$slots.default()[0];
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user