修复 src 结构
This commit is contained in:
73
src/component/popper/calcPosition.ts
Normal file
73
src/component/popper/calcPosition.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Ref } from "vue";
|
||||
// 计算各个方向位置
|
||||
const postionFns: any = {
|
||||
top(
|
||||
el: HTMLElement,
|
||||
popper: HTMLElement,
|
||||
innnerPosition: Ref,
|
||||
called: boolean
|
||||
) {
|
||||
let { top, left, bottom } = el.getBoundingClientRect();
|
||||
if (
|
||||
(top = top - popper.offsetHeight - 6) < 0 &&
|
||||
bottom > popper.offsetHeight
|
||||
) {
|
||||
innnerPosition.value = "bottom";
|
||||
top = bottom;
|
||||
}
|
||||
return {
|
||||
top: `${top}px`,
|
||||
left: `${left - (popper.offsetWidth - el.offsetWidth) / 2}px`,
|
||||
};
|
||||
},
|
||||
bottom(
|
||||
el: HTMLElement,
|
||||
popper: HTMLElement,
|
||||
innnerPosition: Ref,
|
||||
called: boolean
|
||||
) {
|
||||
let { top, left, bottom } = el.getBoundingClientRect();
|
||||
if (window.innerHeight - bottom < popper.offsetHeight + 6) {
|
||||
innnerPosition.value = "top";
|
||||
bottom = top - popper.offsetHeight - 6;
|
||||
}
|
||||
return {
|
||||
top: `${bottom}px`,
|
||||
left: `${left - (popper.offsetWidth - el.offsetWidth) / 2}px`,
|
||||
};
|
||||
},
|
||||
left(
|
||||
el: HTMLElement,
|
||||
popper: HTMLElement,
|
||||
innnerPosition: Ref,
|
||||
called: boolean
|
||||
) {
|
||||
let { top, left, right } = el.getBoundingClientRect();
|
||||
left = left - popper.offsetWidth - 6;
|
||||
if (left < 0) {
|
||||
innnerPosition.value = "right";
|
||||
left = right;
|
||||
}
|
||||
return {
|
||||
top: `${top - (popper.offsetHeight - el.offsetHeight) / 2}px`,
|
||||
left: `${left}px`,
|
||||
};
|
||||
},
|
||||
right(
|
||||
el: HTMLElement,
|
||||
popper: HTMLElement,
|
||||
innnerPosition: Ref,
|
||||
called: boolean
|
||||
) {
|
||||
let { top, left, right } = el.getBoundingClientRect();
|
||||
if (window.innerWidth < right + popper.offsetWidth + 6) {
|
||||
innnerPosition.value = "left";
|
||||
right = left - popper.offsetWidth - 6;
|
||||
}
|
||||
return {
|
||||
top: `${top - (popper.offsetHeight - el.offsetHeight) / 2}px`,
|
||||
left: `${right}px`,
|
||||
};
|
||||
},
|
||||
};
|
||||
export default postionFns;
|
||||
126
src/component/popper/index.less
Normal file
126
src/component/popper/index.less
Normal file
@@ -0,0 +1,126 @@
|
||||
// 主题颜色
|
||||
// 浅色 --> 默认使用
|
||||
@ligh-background: #FFF;
|
||||
@ligh-color: #3a3a3a;
|
||||
|
||||
// 深色
|
||||
@dark-background: #353535;
|
||||
@dark-color: #FFF;
|
||||
|
||||
@border-clor: #cecece;
|
||||
|
||||
// 单一设置主题
|
||||
.single-theme(@position, @contrary_position, @margin_postion, @color, @m-border-color) {
|
||||
@attr : ~'[position=@{position}]';
|
||||
&{
|
||||
border: 1px solid @m-border-color;
|
||||
&@{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;
|
||||
border-@{contrary_position}-width: 0;
|
||||
margin-@{margin_postion}: -6px;
|
||||
border-@{position}-color: @color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 统一设置四个方向的主题
|
||||
.theme(@background-color, @color, @border-color) {
|
||||
background-color: @background-color;
|
||||
color: @color;
|
||||
.single-theme(top, bottom, left, @background-color, @border-color);
|
||||
.single-theme(bottom, top, left, @background-color, @border-color);
|
||||
.single-theme(right, left, top, @background-color, @border-color);
|
||||
.single-theme(left, right, top, @background-color, @border-color);
|
||||
}
|
||||
|
||||
// 箭头默认居中
|
||||
.arrow-default-center(@position, @prop) {
|
||||
@attr : ~'[position=@{position}]';
|
||||
&@{attr} {
|
||||
.layui-popper-arrow{
|
||||
@{prop}: -moz-calc(50% - 6px);
|
||||
@{prop}: -webkit-calc(50% - 6px);
|
||||
@{prop}: calc(50% - 6px);
|
||||
}
|
||||
}
|
||||
}
|
||||
.all-arrow-default-center() {
|
||||
.arrow-default-center(top, left);
|
||||
.arrow-default-center(bottom, left);
|
||||
.arrow-default-center(left, 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 {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
word-wrap: break-word;
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15);
|
||||
.theme(@ligh-background, @ligh-color, @border-clor);
|
||||
max-width: 300px;
|
||||
z-index: 9999;
|
||||
|
||||
// 箭头默认居中
|
||||
.all-arrow-default-center();
|
||||
|
||||
&::after{
|
||||
content: " ";
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
// 填充
|
||||
.fill-popper();
|
||||
|
||||
.layui-popper-arrow {
|
||||
&,&::after{
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::after{
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* 深色主题 */
|
||||
&.layui-dark {
|
||||
.theme(@dark-background, @dark-color, @dark-background);
|
||||
}
|
||||
|
||||
}
|
||||
140
src/component/popper/index.vue
Normal file
140
src/component/popper/index.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<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";
|
||||
export default {
|
||||
name: NAME,
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "./index.less";
|
||||
import postionFns from "./calcPosition";
|
||||
import {
|
||||
CSSProperties,
|
||||
ref,
|
||||
watch,
|
||||
defineEmits,
|
||||
onMounted,
|
||||
} from "vue";
|
||||
import { on } from "../../utils/domUtil";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
el: any;
|
||||
content?: string | Number;
|
||||
position?: string;
|
||||
trigger?: string;
|
||||
enterable?: boolean;
|
||||
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],
|
||||
click: ["click", document, "click", true],
|
||||
};
|
||||
|
||||
const triggerArr = EVENT_MAP[props.trigger];
|
||||
if (!triggerArr) {
|
||||
console.error(`${NAME} render error!cause: 'Trigger' must be 'hover/click' `);
|
||||
}
|
||||
|
||||
const style = ref<CSSProperties>({ top: -window.innerHeight + "px", left: 0 });
|
||||
const checkTarget = ref(false);
|
||||
const popper = ref<HTMLDivElement>({} as HTMLDivElement);
|
||||
const innnerPosition = ref(props.position);
|
||||
const innerVisible = ref(props.visible ?? true);
|
||||
const isExist = ref(false);
|
||||
|
||||
watch(innerVisible, (val) => {
|
||||
invokeShowPosistion();
|
||||
emit("update:visible", val);
|
||||
});
|
||||
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,
|
||||
(val) => {
|
||||
innerVisible.value && invokeShowPosistion();
|
||||
}
|
||||
);
|
||||
|
||||
const doShow = function () {
|
||||
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))
|
||||
)
|
||||
return;
|
||||
// style.value = {top: (-window.innerHeight) + 'px',left:0};
|
||||
// popper.value.remove();
|
||||
if (props.isCanHide !== false) {
|
||||
innerVisible.value = false;
|
||||
}
|
||||
innnerPosition.value = props.position;
|
||||
};
|
||||
|
||||
// 计算位置显示
|
||||
const showPosistion = function () {
|
||||
postionFns[props.position] &&
|
||||
(style.value = postionFns[props.position](
|
||||
props.el,
|
||||
popper.value,
|
||||
innnerPosition
|
||||
));
|
||||
};
|
||||
const invokeShowPosistion = function () {
|
||||
if (innerVisible.value) {
|
||||
popper.value.offsetWidth === 0
|
||||
? setTimeout(showPosistion, 0)
|
||||
: showPosistion();
|
||||
// 延时确保计算位置正确
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user