UNPKG

element-plus

Version:

A Component Library for Vue 3

235 lines (234 loc) 8.2 kB
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, }; };