@arco-design/web-vue
Version:
Arco Design Vue 2.0: A Vue.js 3 UI Library
644 lines (643 loc) • 19.9 kB
JavaScript
"use strict";
var vue = require("vue");
var globalConfig = require("../_utils/global-config.js");
var utils = require("./utils.js");
var resizeObserverV2 = require("../_components/resize-observer-v2.js");
var dom = require("../_utils/dom.js");
var vueUtils = require("../_utils/vue-utils.js");
var usePickSlots = require("../_hooks/use-pick-slots.js");
var context$1 = require("./context.js");
var throttleByRaf = require("../_utils/throttle-by-raf.js");
var usePopupManager = require("../_hooks/use-popup-manager.js");
var useResizeObserver = require("../_hooks/use-resize-observer.js");
var clientOnly = require("../_components/client-only.js");
var useTeleportContainer = require("../_hooks/use-teleport-container.js");
var context = require("../config-provider/context.js");
var useFirstElement = require("../_hooks/use-first-element.js");
var omit = require("../_utils/omit.js");
const TRIGGER_EVENTS = ["onClick", "onMouseenter", "onMouseleave", "onFocusin", "onFocusout", "onContextmenu"];
var _Trigger = vue.defineComponent({
name: "Trigger",
inheritAttrs: false,
props: {
popupVisible: {
type: Boolean,
default: void 0
},
defaultPopupVisible: {
type: Boolean,
default: false
},
trigger: {
type: [String, Array],
default: "hover"
},
position: {
type: String,
default: "bottom"
},
disabled: {
type: Boolean,
default: false
},
popupOffset: {
type: Number,
default: 0
},
popupTranslate: {
type: [Array, Object]
},
showArrow: {
type: Boolean,
default: false
},
alignPoint: {
type: Boolean,
default: false
},
popupHoverStay: {
type: Boolean,
default: true
},
blurToClose: {
type: Boolean,
default: true
},
clickToClose: {
type: Boolean,
default: true
},
clickOutsideToClose: {
type: Boolean,
default: true
},
unmountOnClose: {
type: Boolean,
default: true
},
contentClass: {
type: [String, Array, Object]
},
contentStyle: {
type: Object
},
arrowClass: {
type: [String, Array, Object]
},
arrowStyle: {
type: Object
},
popupStyle: {
type: Object
},
animationName: {
type: String,
default: "fade-in"
},
duration: {
type: [Number, Object]
},
mouseEnterDelay: {
type: Number,
default: 100
},
mouseLeaveDelay: {
type: Number,
default: 100
},
focusDelay: {
type: Number,
default: 0
},
autoFitPopupWidth: {
type: Boolean,
default: false
},
autoFitPopupMinWidth: {
type: Boolean,
default: false
},
autoFixPosition: {
type: Boolean,
default: true
},
popupContainer: {
type: [String, Object]
},
updateAtScroll: {
type: Boolean,
default: false
},
autoFitTransformOrigin: {
type: Boolean,
default: false
},
hideEmpty: {
type: Boolean,
default: false
},
openedClass: {
type: [String, Array, Object]
},
autoFitPosition: {
type: Boolean,
default: true
},
renderToBody: {
type: Boolean,
default: true
},
preventFocus: {
type: Boolean,
default: false
},
scrollToClose: {
type: Boolean,
default: false
},
scrollToCloseDistance: {
type: Number,
default: 0
}
},
emits: {
"update:popupVisible": (visible) => true,
"popupVisibleChange": (visible) => true,
"show": () => true,
"hide": () => true,
"resize": () => true
},
setup(props, {
emit,
slots,
attrs
}) {
const {
popupContainer
} = vue.toRefs(props);
const prefixCls = globalConfig.getPrefixCls("trigger");
const popupAttrs = vue.computed(() => omit.omit(attrs, TRIGGER_EVENTS));
const configCtx = vue.inject(context.configProviderInjectionKey, void 0);
const triggerMethods = vue.computed(() => [].concat(props.trigger));
const childrenRefs = /* @__PURE__ */ new Set();
const triggerCtx = vue.inject(context$1.triggerInjectionKey, void 0);
const {
children,
firstElement
} = useFirstElement.useFirstElement();
const popupRef = vue.ref();
const popupVisible = vue.ref(props.defaultPopupVisible);
const popupPosition = vue.ref(props.position);
const popupStyle = vue.ref({});
const transformStyle = vue.ref({});
const arrowStyle = vue.ref({});
const arrowRef = vue.ref();
const mousePosition = vue.ref({
top: 0,
left: 0
});
let scrollPosition = null;
let windowScrollPosition = null;
const computedVisible = vue.computed(() => {
var _a;
return (_a = props.popupVisible) != null ? _a : popupVisible.value;
});
const {
teleportContainer,
containerRef
} = useTeleportContainer.useTeleportContainer({
popupContainer,
visible: computedVisible,
documentContainer: true
});
const {
zIndex
} = usePopupManager("popup", {
visible: computedVisible
});
let delayTimer = 0;
let outsideListener = false;
let windowListener = false;
const cleanDelayTimer = () => {
if (delayTimer) {
window.clearTimeout(delayTimer);
delayTimer = 0;
}
};
const updateMousePosition = (e) => {
if (props.alignPoint) {
const {
pageX,
pageY
} = e;
mousePosition.value = {
top: pageY,
left: pageX
};
}
};
const updatePopupStyle = () => {
if (!firstElement.value || !popupRef.value || !containerRef.value) {
return;
}
const containerRect = containerRef.value.getBoundingClientRect();
const triggerRect = props.alignPoint ? {
top: mousePosition.value.top,
bottom: mousePosition.value.top,
left: mousePosition.value.left,
right: mousePosition.value.left,
scrollTop: mousePosition.value.top,
scrollBottom: mousePosition.value.top,
scrollLeft: mousePosition.value.left,
scrollRight: mousePosition.value.left,
width: 0,
height: 0
} : utils.getElementScrollRect(firstElement.value, containerRect);
const getPopupRect = () => utils.getElementScrollRect(popupRef.value, containerRect);
const popupRect = getPopupRect();
const {
style,
position
} = utils.getPopupStyle(props.position, containerRect, triggerRect, popupRect, {
offset: props.popupOffset,
translate: props.popupTranslate,
customStyle: props.popupStyle,
autoFitPosition: props.autoFitPosition
});
if (props.autoFitTransformOrigin) {
transformStyle.value = {
transformOrigin: utils.getTransformOrigin(position)
};
}
if (props.autoFitPopupMinWidth) {
style.minWidth = `${triggerRect.width}px`;
} else if (props.autoFitPopupWidth) {
style.width = `${triggerRect.width}px`;
}
if (popupPosition.value !== position) {
popupPosition.value = position;
}
popupStyle.value = style;
if (props.showArrow) {
vue.nextTick(() => {
arrowStyle.value = utils.getArrowStyle(position, triggerRect, getPopupRect(), {
customStyle: props.arrowStyle
});
});
}
};
const changeVisible = (visible, delay) => {
if (visible === computedVisible.value && delayTimer === 0) {
return;
}
const update = () => {
popupVisible.value = visible;
emit("update:popupVisible", visible);
emit("popupVisibleChange", visible);
if (visible) {
vue.nextTick(() => {
updatePopupStyle();
});
}
};
if (!visible) {
scrollPosition = null;
windowScrollPosition = null;
}
if (delay) {
cleanDelayTimer();
if (visible !== computedVisible.value) {
delayTimer = window.setTimeout(update, delay);
}
} else {
update();
}
};
const handleClick = (e) => {
var _a;
(_a = attrs.onClick) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || computedVisible.value && !props.clickToClose) {
return;
}
if (triggerMethods.value.includes("click")) {
updateMousePosition(e);
changeVisible(!computedVisible.value);
} else if (triggerMethods.value.includes("contextMenu") && computedVisible.value) {
changeVisible(false);
}
};
const handleMouseEnter = (e) => {
var _a;
(_a = attrs.onMouseenter) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || !triggerMethods.value.includes("hover")) {
return;
}
updateMousePosition(e);
changeVisible(true, props.mouseEnterDelay);
};
const handleMouseEnterWithContext = (e) => {
triggerCtx == null ? void 0 : triggerCtx.onMouseenter(e);
handleMouseEnter(e);
};
const handleMouseLeave = (e) => {
var _a;
(_a = attrs.onMouseleave) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || !triggerMethods.value.includes("hover")) {
return;
}
changeVisible(false, props.mouseLeaveDelay);
};
const handleMouseLeaveWithContext = (e) => {
triggerCtx == null ? void 0 : triggerCtx.onMouseleave(e);
handleMouseLeave(e);
};
const handleFocusin = (e) => {
var _a;
(_a = attrs.onFocusin) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || !triggerMethods.value.includes("focus")) {
return;
}
changeVisible(true, props.focusDelay);
};
const handleFocusout = (e) => {
var _a;
(_a = attrs.onFocusout) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || !triggerMethods.value.includes("focus")) {
return;
}
if (!props.blurToClose) {
return;
}
changeVisible(false);
};
const handleContextmenu = (e) => {
var _a;
(_a = attrs.onContextmenu) == null ? void 0 : _a.call(attrs, e);
if (props.disabled || !triggerMethods.value.includes("contextMenu") || computedVisible.value && !props.clickToClose) {
return;
}
updateMousePosition(e);
changeVisible(!computedVisible.value);
e.preventDefault();
};
const addChildRef = (ref) => {
childrenRefs.add(ref);
triggerCtx == null ? void 0 : triggerCtx.addChildRef(ref);
};
const removeChildRef = (ref) => {
childrenRefs.delete(ref);
triggerCtx == null ? void 0 : triggerCtx.removeChildRef(ref);
};
vue.provide(context$1.triggerInjectionKey, vue.reactive({
onMouseenter: handleMouseEnterWithContext,
onMouseleave: handleMouseLeaveWithContext,
addChildRef,
removeChildRef
}));
const removeOutsideListener = () => {
dom.off(document.documentElement, "mousedown", handleOutsideClick);
outsideListener = false;
};
const contentSlot = usePickSlots(slots, "content");
const hidePopup = vue.computed(() => {
var _a;
return props.hideEmpty && vueUtils.isEmptyChildren((_a = contentSlot.value) == null ? void 0 : _a.call(contentSlot));
});
const handleOutsideClick = (e) => {
var _a, _b, _c;
if (((_a = firstElement.value) == null ? void 0 : _a.contains(e.target)) || ((_b = popupRef.value) == null ? void 0 : _b.contains(e.target))) {
return;
}
for (const item of childrenRefs) {
if ((_c = item.value) == null ? void 0 : _c.contains(e.target)) {
return;
}
}
removeOutsideListener();
changeVisible(false);
};
const isExceedThreshold = (oldPosition, element) => {
const [scrollTop, scrollLeft] = oldPosition;
const {
scrollTop: newScrollTop,
scrollLeft: newScrollLeft
} = element;
return Math.abs(newScrollTop - scrollTop) >= props.scrollToCloseDistance || Math.abs(newScrollLeft - scrollLeft) >= props.scrollToCloseDistance;
};
const handleScroll = throttleByRaf.throttleByRaf((e) => {
if (computedVisible.value) {
if (props.scrollToClose || (configCtx == null ? void 0 : configCtx.scrollToClose)) {
const element = e.target;
if (!scrollPosition) {
scrollPosition = [element.scrollTop, element.scrollLeft];
}
if (isExceedThreshold(scrollPosition, element)) {
changeVisible(false);
} else {
updatePopupStyle();
}
} else {
updatePopupStyle();
}
}
});
const removeWindowScroll = () => {
dom.off(window, "scroll", onWindowScroll);
windowListener = false;
};
const onWindowScroll = throttleByRaf.throttleByRaf((e) => {
const element = e.target.documentElement;
if (!windowScrollPosition) {
windowScrollPosition = [element.scrollTop, element.scrollLeft];
}
if (isExceedThreshold(windowScrollPosition, element)) {
changeVisible(false);
removeWindowScroll();
}
});
const handleResize = () => {
if (computedVisible.value) {
updatePopupStyle();
}
};
const onTargetResize = () => {
handleResize();
emit("resize");
};
const handlePopupMouseDown = (e) => {
if (props.preventFocus) {
e.preventDefault();
}
};
triggerCtx == null ? void 0 : triggerCtx.addChildRef(popupRef);
const triggerCls = vue.computed(() => {
return computedVisible.value ? props.openedClass : void 0;
});
let scrollElements;
vue.watch(computedVisible, (value) => {
if (props.clickOutsideToClose) {
if (!value && outsideListener) {
removeOutsideListener();
} else if (value && !outsideListener) {
dom.on(document.documentElement, "mousedown", handleOutsideClick);
outsideListener = true;
}
}
if (props.scrollToClose || (configCtx == null ? void 0 : configCtx.scrollToClose)) {
dom.on(window, "scroll", onWindowScroll);
windowListener = true;
}
if (props.updateAtScroll || (configCtx == null ? void 0 : configCtx.updateAtScroll)) {
if (value) {
scrollElements = utils.getScrollElements(firstElement.value);
for (const item of scrollElements) {
item.addEventListener("scroll", handleScroll);
}
} else if (scrollElements) {
for (const item of scrollElements) {
item.removeEventListener("scroll", handleScroll);
}
scrollElements = void 0;
}
}
if (value) {
mounted.value = true;
}
});
vue.watch(() => [props.autoFitPopupWidth, props.autoFitPopupMinWidth], () => {
if (computedVisible.value) {
updatePopupStyle();
}
});
const {
createResizeObserver,
destroyResizeObserver
} = useResizeObserver.useResizeObserver({
elementRef: containerRef,
onResize: handleResize
});
vue.onMounted(() => {
createResizeObserver();
if (computedVisible.value) {
updatePopupStyle();
if (props.clickOutsideToClose && !outsideListener) {
dom.on(document.documentElement, "mousedown", handleOutsideClick);
outsideListener = true;
}
if (props.updateAtScroll || (configCtx == null ? void 0 : configCtx.updateAtScroll)) {
scrollElements = utils.getScrollElements(firstElement.value);
for (const item of scrollElements) {
item.addEventListener("scroll", handleScroll);
}
}
}
});
vue.onUpdated(() => {
if (computedVisible.value) {
updatePopupStyle();
}
});
vue.onDeactivated(() => {
changeVisible(false);
});
vue.onBeforeUnmount(() => {
triggerCtx == null ? void 0 : triggerCtx.removeChildRef(popupRef);
destroyResizeObserver();
if (outsideListener) {
removeOutsideListener();
}
if (windowListener) {
removeWindowScroll();
}
if (scrollElements) {
for (const item of scrollElements) {
item.removeEventListener("scroll", handleScroll);
}
scrollElements = void 0;
}
});
const mounted = vue.ref(computedVisible.value);
const isAnimation = vue.ref(false);
const onAnimationStart = () => {
isAnimation.value = true;
};
const handleShow = () => {
isAnimation.value = false;
if (computedVisible.value) {
emit("show");
}
};
const handleHide = () => {
isAnimation.value = false;
if (!computedVisible.value) {
mounted.value = false;
emit("hide");
}
};
return () => {
var _a, _b;
children.value = (_b = (_a = slots.default) == null ? void 0 : _a.call(slots)) != null ? _b : [];
vueUtils.mergeFirstChild(children.value, {
class: triggerCls.value,
onClick: handleClick,
onMouseenter: handleMouseEnter,
onMouseleave: handleMouseLeave,
onFocusin: handleFocusin,
onFocusout: handleFocusout,
onContextmenu: handleContextmenu
});
return vue.createVNode(vue.Fragment, null, [props.autoFixPosition ? vue.createVNode(resizeObserverV2, {
"onResize": onTargetResize
}, {
default: () => [children.value]
}) : children.value, vue.createVNode(clientOnly, null, {
default: () => [vue.createVNode(vue.Teleport, {
"to": teleportContainer.value,
"disabled": !props.renderToBody
}, {
default: () => [(!props.unmountOnClose || computedVisible.value || mounted.value) && !hidePopup.value && vue.createVNode(resizeObserverV2, {
"onResize": handleResize
}, {
default: () => [vue.createVNode("div", vue.mergeProps({
"ref": popupRef,
"class": [`${prefixCls}-popup`, `${prefixCls}-position-${popupPosition.value}`],
"style": {
...popupStyle.value,
zIndex: zIndex.value,
pointerEvents: isAnimation.value ? "none" : "auto"
},
"trigger-placement": popupPosition.value,
"onMouseenter": handleMouseEnterWithContext,
"onMouseleave": handleMouseLeaveWithContext,
"onMousedown": handlePopupMouseDown
}, popupAttrs.value), [vue.createVNode(vue.Transition, {
"name": props.animationName,
"duration": props.duration,
"appear": true,
"onBeforeEnter": onAnimationStart,
"onAfterEnter": handleShow,
"onBeforeLeave": onAnimationStart,
"onAfterLeave": handleHide
}, {
default: () => {
var _a2;
return [vue.withDirectives(vue.createVNode("div", {
"class": `${prefixCls}-popup-wrapper`,
"style": transformStyle.value
}, [vue.createVNode("div", {
"class": [`${prefixCls}-content`, props.contentClass],
"style": props.contentStyle
}, [(_a2 = slots.content) == null ? void 0 : _a2.call(slots)]), props.showArrow && vue.createVNode("div", {
"ref": arrowRef,
"class": [`${prefixCls}-arrow`, props.arrowClass],
"style": arrowStyle.value
}, null)]), [[vue.vShow, computedVisible.value]])];
}
})])]
})]
})]
})]);
};
}
});
module.exports = _Trigger;