@opensig/opendesign
Version:
474 lines (473 loc) • 16 kB
JavaScript
import { defineComponent, computed, ref, reactive, toRefs, onMounted, watch, onUnmounted, createElementBlock, openBlock, Fragment, createBlock, createCommentVNode, unref, withCtx, renderSlot, Teleport, createVNode, mergeProps, Transition, withDirectives, createElementVNode, normalizeStyle, normalizeClass, vShow, nextTick } from "vue";
import { popupProps } from "./types.mjs";
import { isHtmlElement, getScrollParents } from "../_utils/dom.mjs";
import { debounce, throttleRAF } from "../_utils/helper.mjs";
import { isArray, isTouchDevice, isFunction } from "../_utils/is.mjs";
import { bindTrigger, calcPopupStyle, getTransformOrigin } from "./popup.mjs";
import { useResizeObserver } from "../hooks/use-resize-observer.mjs";
import OResizeObserver from "../resize-observer/resize-observer.mjs";
import { useIntersectionObserver } from "../hooks/use-intersection-observer.mjs";
import "../hooks/use-theme.mjs";
import "../_utils/global.mjs";
import "@vueuse/core";
import { getHtmlElement, resolveHtmlElement } from "../_utils/vue-utils.mjs";
import OChildOnly from "../child-only/child-only.mjs";
import ClientOnly from "../_components/client-only.mjs";
import { createTopZIndex, removeZIndex } from "../_utils/z-index.mjs";
const __default__ = {
inheritAttrs: false
};
const _sfc_main = /* @__PURE__ */ defineComponent({
...__default__,
__name: "OPopup",
props: popupProps,
emits: ["update:visible", "change"],
setup(__props, { emit: __emit }) {
const props = __props;
const emits = __emit;
const triggers = computed(() => {
const triggers2 = isArray(props.trigger) ? props.trigger : [props.trigger];
if (isTouchDevice) {
const hasClick = triggers2.some((item) => item === "click" || item === "click-outclick");
return hasClick || triggers2.includes("none") ? triggers2 : [...triggers2, "click"];
}
return triggers2;
});
const visible = ref(false);
const targetElRef = ref(null);
let targetEl = null;
const isTargetInViewport = ref(true);
const wrapperEl = ref(null);
const popupRef = ref(null);
const popStyle = reactive({
"--popup-edge-offset": `${props.edgeOffset}px`,
// left, top 恒为 0px
left: "0px",
top: "0px"
});
const popPosition = ref(props.position);
const wrapOrigin = ref({ left: "0px", top: "0px" });
const wrapStyle = computed(() => ({
transformOrigin: `${wrapOrigin.value.left} ${wrapOrigin.value.top}`
}));
const anchorStyle = ref({});
const toMount = ref(false);
const isAnimating = ref(false);
let ro = null;
let io = null;
const updateZIndex = (show) => {
if (show) {
popStyle["--popup-z-index"] = createTopZIndex();
} else {
removeZIndex(popStyle["--popup-z-index"]);
}
};
const { target, wrapper } = toRefs(props);
onMounted(() => {
ro = useResizeObserver();
io = useIntersectionObserver();
visible.value = props.visible;
if (props.visible) {
updateZIndex(props.visible);
}
});
onMounted(() => {
watch(
target,
(newVal) => {
if (newVal && targetEl) {
ro == null ? void 0 : ro.unobserve(targetEl, onResize);
}
if (newVal) {
const el = getHtmlElement(newVal);
if (el) {
bindTargetEvent(el);
updatePopupStyle();
}
}
},
{ immediate: true }
);
});
onMounted(() => {
watch(
wrapper,
() => {
if (wrapperEl.value) {
ro == null ? void 0 : ro.unobserve(wrapperEl.value, onResize);
}
resolveHtmlElement(wrapper).then((el) => {
if (el) {
wrapperEl.value = el;
}
});
},
{ immediate: true }
);
});
let triggerListener = [];
const removeTriggerListener = () => triggerListener.forEach((fn) => fn());
const bindTargetEvent = (el) => {
if (!el) {
return;
}
removeTriggerListener();
targetEl = el;
if (props.adjustMinWidth) {
popStyle.minWidth = `${targetEl.offsetWidth}px`;
} else if (props.adjustWidth) {
popStyle.width = `${targetEl.offsetWidth}px`;
}
triggerListener = bindTrigger({
el,
popupRef,
triggers: triggers.value,
updateFn: setVisible,
hoverDelay: props.hoverDelay,
autoHide: props.autoHide
});
if (props.hideWhenTargetInvisible) {
io == null ? void 0 : io.observe(targetEl, onTargetInterscting);
}
};
onUnmounted(() => {
removeTriggerListener();
if (wrapperEl.value) {
ro == null ? void 0 : ro.unobserve(wrapperEl.value, onResize);
}
if (targetEl) {
ro == null ? void 0 : ro.unobserve(targetEl, onResize);
}
});
const isHiddenWhenTargetOutViewport = () => props.hideWhenTargetInvisible && !isTargetInViewport.value;
const updatePopupStyle = () => {
if (isHiddenWhenTargetOutViewport()) {
return;
}
if (!targetEl || !popupRef.value || !popupContent.value) {
return;
}
const {
popupStyle: pStyle,
position,
anchorStyle: aStyle
} = calcPopupStyle({
popupEl: popupRef.value,
targetEl,
position: props.position,
adaptive: props.adaptive,
offset: props.offset,
edgeOffset: props.edgeOffset,
anchor: props.anchor
});
wrapOrigin.value = getTransformOrigin(position);
popPosition.value = position;
popStyle.transform = `translate(${pStyle.left}px, ${pStyle.top}px)`;
anchorStyle.value = aStyle;
};
let oldIntersecting = null;
const onTargetInterscting = (entry) => {
isTargetInViewport.value = entry.isIntersecting;
if (oldIntersecting !== null && entry.isIntersecting) {
if (visible.value) {
nextTick(() => {
updatePopupStyle();
});
}
}
oldIntersecting = isTargetInViewport.value;
};
const beforeToggle = async (show) => {
let goon = true;
if (show) {
if (isFunction(props.beforeShow)) {
goon = await props.beforeShow();
}
} else {
if (isFunction(props.beforeHide)) {
goon = await props.beforeHide();
}
}
return goon !== false;
};
watch(
() => props.visible,
async (val) => {
setVisible(val);
}
);
let visibleTimer = 0;
const clearVisibleTimer = () => {
if (visibleTimer) {
window.clearTimeout(visibleTimer);
visibleTimer = 0;
}
};
const applyVisible = (isVisible) => {
visible.value = isVisible;
updateZIndex(isVisible);
if (props.visible !== isVisible) {
emits("update:visible", isVisible);
emits("change", isVisible);
}
if (visible.value) {
toMount.value = true;
if (props.hideWhenTargetInvisible && targetEl) {
io == null ? void 0 : io.observe(targetEl, onTargetInterscting);
}
}
};
let visibleToggleId = 0;
const setVisible = async (isVisible, delay) => {
if (props.disabled) {
return;
}
const currentVisibleToggleId = ++visibleToggleId;
const v = isVisible ?? !visible.value;
if (v === visible.value && visibleTimer === 0) {
return;
}
if (!await beforeToggle(v)) {
return;
}
if (currentVisibleToggleId !== visibleToggleId) {
return;
}
clearVisibleTimer();
if (delay) {
visibleTimer = window.setTimeout(
() => {
applyVisible(v);
visibleTimer = 0;
},
delay
/** delay 时间相同,无竞态问题 */
);
} else {
applyVisible(v);
}
};
watch(targetElRef, (elRef) => {
if (isHtmlElement(elRef == null ? void 0 : elRef.$el)) {
bindTargetEvent(elRef == null ? void 0 : elRef.$el);
}
});
const onResize = (_en, isFirst) => {
if (visible.value && !isFirst) {
updatePopupStyle();
}
};
const onPopupResize = debounce(
(en) => {
onResize(en, false);
},
100,
true,
true
);
const handleTransitionStart = () => {
isAnimating.value = true;
};
const popupContent = ref();
const checkVisibleState = debounce(
() => {
if (popupContent.value && visible.value === false && popupContent.value.style.display !== "none") {
popupContent.value.style.display = "none";
}
},
200,
// 动画播放时间
false
);
const onBeforeLeave = () => {
checkVisibleState();
handleTransitionStart();
};
const handleTransitionEnd = () => {
isAnimating.value = false;
if (!visible.value && props.unmountOnHide) {
toMount.value = false;
}
};
const scrollListener = throttleRAF(() => {
if (visible.value) {
updatePopupStyle();
}
});
const listenScroll = (el) => {
el.addEventListener("scroll", scrollListener, { passive: true });
return () => {
el.removeEventListener("scroll", scrollListener);
};
};
watch(popupRef, (popEl) => {
let handles = [];
if (popEl) {
if (targetEl) {
const scrollers = getScrollParents(targetEl);
handles = scrollers.map((el) => {
return listenScroll(el);
});
handles.push(listenScroll(window));
ro == null ? void 0 : ro.observe(targetEl, (en, isFirst) => {
if (props.adjustMinWidth) {
popStyle.minWidth = `${targetEl == null ? void 0 : targetEl.offsetWidth}px`;
} else if (props.adjustWidth) {
popStyle.width = `${targetEl == null ? void 0 : targetEl.offsetWidth}px`;
}
onResize(en, isFirst);
});
}
if (wrapperEl.value) {
ro == null ? void 0 : ro.observe(wrapperEl.value, onResize);
}
} else {
handles.forEach((hl) => hl());
if (wrapperEl.value) {
ro == null ? void 0 : ro.unobserve(wrapperEl.value, onResize);
}
if (targetEl) {
ro == null ? void 0 : ro.unobserve(targetEl, onResize);
io == null ? void 0 : io.unobserve(targetEl, onTargetInterscting);
isTargetInViewport.value = true;
}
}
});
const onPopupHoverIn = () => {
if (triggers.value.includes("hover")) {
setVisible(true, props.hoverDelay);
}
};
const onPopupHoverOut = () => {
if (triggers.value.includes("hover") && props.autoHide) {
setVisible(false, props.hoverDelay);
}
};
const shouldMount = computed(() => {
return toMount.value || visible.value || !props.unmountOnHide;
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock(
Fragment,
null,
[
_ctx.$slots.target ? (openBlock(), createBlock(
unref(OChildOnly),
{
key: 0,
ref_key: "targetElRef",
ref: targetElRef
},
{
default: withCtx(() => [
renderSlot(_ctx.$slots, "target")
]),
_: 3
/* FORWARDED */
},
512
/* NEED_PATCH */
)) : createCommentVNode("v-if", true),
!props.disabled ? (openBlock(), createBlock(unref(ClientOnly), { key: 1 }, {
default: withCtx(() => [
(openBlock(), createBlock(Teleport, {
to: props.wrapper,
disabled: !props.wrapper
}, [
createVNode(unref(OResizeObserver), { onResize: unref(onPopupResize) }, {
default: withCtx(() => [
shouldMount.value ? (openBlock(), createElementBlock(
"div",
mergeProps({
key: 0,
ref_key: "popupRef",
ref: popupRef,
class: ["o-popup", [
`o-popup-pos-${popPosition.value}`,
{
"out-view": props.hideWhenTargetInvisible && !isTargetInViewport.value,
animating: isAnimating.value
}
]],
style: popStyle
}, _ctx.$attrs, {
onMouseenter: onPopupHoverIn,
onMouseleave: onPopupHoverOut
}),
[
createVNode(Transition, {
name: props.transition,
appear: true,
onBeforeEnter: handleTransitionStart,
onAfterEnter: handleTransitionEnd,
onBeforeLeave,
onAfterLeave: handleTransitionEnd,
persisted: ""
}, {
default: withCtx(() => [
withDirectives(createElementVNode(
"div",
{
ref_key: "popupContent",
ref: popupContent,
class: normalizeClass(["o-popup-wrap", props.wrapClass]),
style: normalizeStyle(wrapStyle.value)
},
[
createElementVNode(
"div",
{
class: normalizeClass(["o-popup-body", props.bodyClass])
},
[
renderSlot(_ctx.$slots, "default")
],
2
/* CLASS */
),
props.anchor ? (openBlock(), createElementBlock(
"div",
{
key: 0,
class: normalizeClass(["o-popup-anchor", props.anchorClass]),
style: normalizeStyle(anchorStyle.value)
},
[
renderSlot(_ctx.$slots, "anchor")
],
6
/* CLASS, STYLE */
)) : createCommentVNode("v-if", true)
],
6
/* CLASS, STYLE */
), [
[vShow, visible.value]
])
]),
_: 3
/* FORWARDED */
}, 8, ["name"])
],
16
/* FULL_PROPS */
)) : createCommentVNode("v-if", true)
]),
_: 3
/* FORWARDED */
}, 8, ["onResize"])
], 8, ["to", "disabled"]))
]),
_: 3
/* FORWARDED */
})) : createCommentVNode("v-if", true)
],
64
/* STABLE_FRAGMENT */
);
};
}
});
export {
_sfc_main as default
};