layui/.svn/pristine/e3/e3f48c6294facda8eeaa874952341a6430b2d0b3.svn-base
2022-12-09 16:41:41 +08:00

277 lines
6.4 KiB
Plaintext

<template>
<teleport to="body" v-if="isExist">
<transition v-show="innerVisible">
<div
ref="popperRefEl"
:class="['layui-popper', { 'layui-dark': isDark }, props.popperClass]"
:style="[style, props.popperStyle ?? '']"
:position="innnerPosition"
@mouseenter="handlerPopperMouseEnter"
@mouseleave="handlerPopperMouseLeave"
>
<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,
onMounted,
nextTick,
useSlots,
shallowRef,
computed,
toRef,
StyleValue,
Ref,
} from "vue";
import {
onClickOutside,
useEventListener,
useResizeObserver,
useThrottleFn,
} from "@vueuse/core";
export type PopperTrigger = "click" | "hover" | "focus" | "contextMenu";
export interface PopperProps {
el: HTMLElement;
position?: string;
enterable?: boolean;
isDark?: boolean;
disabled?: boolean;
isCanHide?: boolean;
isAutoShow?: boolean;
visible?: boolean;
content?: string | Number;
trigger?: PopperTrigger | PopperTrigger[];
popperClass?: string | Array<string | object> | object;
popperStyle?: StyleValue;
}
const props = withDefaults(defineProps<PopperProps>(), {
position: "top",
isDark: true,
disabled: false,
enterable: true,
isCanHide: true,
isAutoShow: false,
trigger: "hover",
visible: false,
});
const slots = useSlots();
const style = ref<CSSProperties>({ top: -window.innerHeight + "px", left: 0 });
const triggerRefEl = toRef(props, "el");
const popperRefEl = shallowRef<HTMLDivElement>({} as HTMLDivElement);
const innnerPosition = ref(props.position);
const innerVisible = ref(props.visible);
const isExist = ref(props.visible);
let scrollElements: HTMLElement[] | undefined;
const triggerMethods = computed(() =>
([] as Array<PopperTrigger>).concat(props.trigger)
);
const doShow = function () {
if (!props.disabled) {
if (!isExist.value) {
isExist.value = true;
nextTick(() => {
innerVisible.value = true;
});
} else {
innerVisible.value = true;
}
}
};
const doHidden = function (e?: MouseEvent) {
// isCanHide参数由外控制
if (props.isCanHide === false) {
return;
}
innerVisible.value = false;
innnerPosition.value = props.position;
style.value = { top: -window.innerHeight + "px", left: 0 };
};
const calcPosistion = function () {
postionFns[props.position] &&
(style.value = postionFns[props.position](
triggerRefEl.value,
popperRefEl.value,
innnerPosition
));
};
const updatePosistion = function () {
if (innerVisible.value) {
popperRefEl.value.offsetWidth === 0
? nextTick(() => calcPosistion())
: calcPosistion();
nextTick(() => {
calcPosistion();
});
}
};
const handlerPopperMouseEnter = function () {
if (triggerMethods.value.includes("hover") && props.enterable) {
doShow();
}
};
const handlerPopperMouseLeave = function () {
if (triggerMethods.value.includes("hover") && props.enterable) {
doHidden();
}
};
const handlerTriggerClick = function () {
if (!triggerMethods.value.includes("click")) return;
if (innerVisible.value) {
doHidden();
} else {
doShow();
}
};
const handleTriggerContextMenu = function (e: MouseEvent) {
if (!triggerMethods.value.includes("contextMenu")) return;
e.preventDefault();
if (innerVisible.value) {
doHidden();
} else {
doShow();
}
};
const handlerTriggerMouseEnter = function () {
if (!triggerMethods.value.includes("hover")) return;
doShow();
};
const handlerTriggerMouseLeave = function () {
if (!triggerMethods.value.includes("hover")) return;
doHidden();
};
const handleTriggerFocusin = function () {
if (triggerMethods.value.includes("focus") && props.enterable) {
doShow();
}
};
const handleTriggerFocusout = function () {
if (triggerMethods.value.includes("focus") && props.enterable) {
doHidden();
}
};
const handlerTriggerEventRegist = function () {
useEventListener(triggerRefEl.value, "click", handlerTriggerClick);
useEventListener(triggerRefEl.value, "contextmenu", handleTriggerContextMenu);
useEventListener(triggerRefEl.value, "mouseenter", handlerTriggerMouseEnter);
useEventListener(triggerRefEl.value, "mouseleave", handlerTriggerMouseLeave);
useEventListener(triggerRefEl.value, "focusin", handleTriggerFocusin);
useEventListener(triggerRefEl.value, "focusout", handleTriggerFocusout);
};
const handleScroll = useThrottleFn(() => {
if (innerVisible.value) {
updatePosistion();
}
}, 15);
onClickOutside(
triggerRefEl.value,
(e) => {
if (
!innerVisible.value ||
triggerRefEl.value.contains(e.target as HTMLElement) ||
popperRefEl.value.contains(e.target as HTMLElement)
) {
return;
}
doHidden();
},
{
ignore: [popperRefEl.value],
}
);
useResizeObserver(triggerRefEl, () => {
updatePosistion();
});
let popperObserver: { stop: any; isSupported?: Ref<boolean> } | undefined =
undefined;
watch(innerVisible, (isShow) => {
updatePosistion();
if (isShow) {
popperObserver = useResizeObserver(popperRefEl, () => {
updatePosistion();
});
} else {
popperObserver && popperObserver.stop();
}
});
watch(
() => props.visible,
(isShow) => (isShow ? doShow() : doHidden())
);
watch(
() => props.content,
() => {
updatePosistion();
}
);
const isScrollElement = function (element: HTMLElement) {
return (
element.scrollHeight > element.offsetHeight ||
element.scrollWidth > element.offsetWidth
);
};
const getScrollElements = function (container: HTMLElement | undefined) {
const scrollElements: HTMLElement[] = [];
let element: HTMLElement | undefined = container;
while (element && element !== document.documentElement) {
if (isScrollElement(element)) {
scrollElements.push(element);
}
element = element.parentElement ?? undefined;
}
return scrollElements;
};
onMounted(() => {
updatePosistion();
scrollElements = getScrollElements(triggerRefEl.value);
for (const item of scrollElements) {
useEventListener(item, "scroll", handleScroll);
}
useEventListener("resize", handleScroll);
handlerTriggerEventRegist();
});
</script>