element-plus
Version:
A Component Library for Vue 3
235 lines (234 loc) • 8.2 kB
JavaScript
import { cloneVNode, computed, Fragment, getCurrentInstance, h, nextTick, toDisplayString, toRef, Transition, ref, renderSlot, withDirectives, } from 'vue';
import { NOOP } from '@vue/shared';
import { createPopper } from '@popperjs/core';
import { ClickOutside } from 'element-plus/es/directives';
import { generateId, isHTMLElement, isString, refAttacher, } from 'element-plus/es/utils/util';
import { getFirstValidNode } from 'element-plus/es/utils/vnode';
import { stop } from 'element-plus/es/utils/dom';
import PopupManager from 'element-plus/es/utils/popup-manager';
import { throwError } from 'element-plus/es/utils/error';
import useTeleport from '../use-teleport';
import useTimeout from '../use-timeout';
import { useModelToggle } from '../use-model-toggle';
import { useTransitionFallthrough } from '../use-transition-fallthrough';
import { defaultPopperOptions, defaultModifiers } from './use-popper-options';
import { useTargetEvents, DEFAULT_TRIGGER } from './use-target-events';
export const DARK_EFFECT = 'dark';
export const LIGHT_EFFECT = 'light';
export const usePopperControlProps = {
appendToBody: {
type: Boolean,
default: true,
},
arrowOffset: {
type: Number,
},
popperOptions: defaultPopperOptions,
popperClass: {
type: String,
default: '',
},
};
export const usePopperProps = Object.assign(Object.assign({}, usePopperControlProps), { autoClose: {
type: Number,
default: 0,
}, content: {
type: String,
default: '',
}, class: String, style: Object, hideAfter: {
type: Number,
default: 200,
}, disabled: {
type: Boolean,
default: false,
}, effect: {
type: String,
default: DARK_EFFECT,
}, enterable: {
type: Boolean,
default: true,
}, manualMode: {
type: Boolean,
default: false,
}, showAfter: {
type: Number,
default: 0,
}, pure: {
type: Boolean,
default: false,
}, showArrow: {
type: Boolean,
default: true,
}, transition: {
type: String,
default: 'el-fade-in-linear',
}, trigger: {
type: [String, Array],
default: DEFAULT_TRIGGER,
}, visible: {
type: Boolean,
default: undefined,
}, stopPopperMouseEvent: {
type: Boolean,
default: true,
} });
export const usePopperHook = () => {
const vm = getCurrentInstance();
const props = vm.props;
const { slots } = vm;
const arrowRef = ref(null);
const triggerRef = ref(null);
const popperRef = ref(null);
const popperStyle = ref({ zIndex: PopupManager.nextZIndex() });
const visible = ref(false);
const isManual = computed(() => props.manualMode || props.trigger === 'manual');
const popperId = `el-popper-${generateId()}`;
let popperInstance = null;
const { renderTeleport, showTeleport, hideTeleport } = useTeleport(popupRenderer, toRef(props, 'appendToBody'));
const { show, hide } = useModelToggle({
indicator: visible,
onShow,
onHide,
});
const { registerTimeout, cancelTimeout } = useTimeout();
function onShow() {
popperStyle.value.zIndex = PopupManager.nextZIndex();
nextTick(initializePopper);
}
function onHide() {
hideTeleport();
nextTick(detachPopper);
}
function delayShow() {
if (isManual.value || props.disabled)
return;
showTeleport();
registerTimeout(show, props.showAfter);
}
function delayHide() {
if (isManual.value)
return;
registerTimeout(hide, props.hideAfter);
}
function onToggle() {
if (visible.value) {
delayShow();
}
else {
delayHide();
}
}
function detachPopper() {
var _a;
(_a = popperInstance === null || popperInstance === void 0 ? void 0 : popperInstance.destroy) === null || _a === void 0 ? void 0 : _a.call(popperInstance);
popperInstance = null;
}
function onPopperMouseEnter() {
if (props.enterable && props.trigger !== 'click') {
cancelTimeout();
}
}
function onPopperMouseLeave() {
const { trigger } = props;
const shouldPrevent = (isString(trigger) && (trigger === 'click' || trigger === 'focus')) ||
(trigger.length === 1 &&
(trigger[0] === 'click' || trigger[0] === 'focus'));
if (shouldPrevent)
return;
delayHide();
}
function initializePopper() {
if (!visible.value || popperInstance !== null) {
return;
}
const unwrappedTrigger = triggerRef.value;
const $el = isHTMLElement(unwrappedTrigger)
? unwrappedTrigger
: unwrappedTrigger.$el;
popperInstance = createPopper($el, popperRef.value, buildPopperOptions());
popperInstance.update();
}
function buildPopperOptions() {
const modifiers = [...defaultModifiers, ...props.popperOptions.modifiers];
if (props.showArrow) {
modifiers.push({
name: 'arrow',
options: {
padding: props.arrowOffset || 5,
element: arrowRef.value,
},
});
}
return Object.assign(Object.assign({}, props.popperOptions), { modifiers });
}
const { onAfterEnter, onAfterLeave, onBeforeEnter, onBeforeLeave } = useTransitionFallthrough();
const events = useTargetEvents(delayShow, delayHide, onToggle);
const arrowRefAttacher = refAttacher(arrowRef);
const popperRefAttacher = refAttacher(popperRef);
const triggerRefAttacher = refAttacher(triggerRef);
function popupRenderer() {
const mouseUpAndDown = props.stopPopperMouseEvent ? stop : NOOP;
return h(Transition, {
name: props.transition,
onAfterEnter,
onAfterLeave,
onBeforeEnter,
onBeforeLeave,
}, {
default: () => () => visible.value
? h('div', {
'aria-hidden': false,
class: [
props.popperClass,
'el-popper',
`is-${props.effect}`,
props.pure ? 'is-pure' : '',
],
style: popperStyle.value,
id: popperId,
ref: popperRefAttacher,
role: 'tooltip',
onMouseenter: onPopperMouseEnter,
onMouseleave: onPopperMouseLeave,
onClick: stop,
onMousedown: mouseUpAndDown,
onMouseup: mouseUpAndDown,
}, [
renderSlot(slots, 'default', {}, () => [
toDisplayString(props.content),
]),
arrowRenderer(),
])
: null,
});
}
function arrowRenderer() {
return props.showArrow
? h('div', {
ref: arrowRefAttacher,
class: 'el-popper__arrow',
'data-popper-arrow': '',
}, null)
: null;
}
function triggerRenderer(triggerProps) {
var _a;
const trigger = (_a = slots.trigger) === null || _a === void 0 ? void 0 : _a.call(slots);
const firstElement = getFirstValidNode(trigger, 1);
if (!firstElement)
throwError('renderTrigger', 'trigger expects single rooted node');
return cloneVNode(firstElement, triggerProps, true);
}
function render() {
const trigger = triggerRenderer(Object.assign({ 'aria-describedby': popperId, class: props.class, style: props.style, ref: triggerRefAttacher }, events));
return h(Fragment, null, [
isManual.value
? trigger
: withDirectives(trigger, [[ClickOutside, delayHide]]),
renderTeleport(),
]);
}
return {
render,
};
};