UNPKG

naive-ui

Version:

A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast

484 lines 14.7 kB
import { zindexable } from 'vdirs'; import { useCompitable, useIsMounted, useMemo, useMergedState } from 'vooks'; import { cloneVNode, computed, defineComponent, h, provide, ref, Text, toRef, watchEffect, withDirectives } from 'vue'; import { VBinder, VTarget } from 'vueuc'; import { useTheme } from "../../_mixins/index.mjs"; import { call, getFirstSlotVNode, keep, useAdjustedTo, warnOnce } from "../../_utils/index.mjs"; import NPopoverBody, { popoverBodyProps } from "./PopoverBody.mjs"; const bodyPropKeys = Object.keys(popoverBodyProps); const triggerEventMap = { focus: ['onFocus', 'onBlur'], click: ['onClick'], hover: ['onMouseenter', 'onMouseleave'], manual: [], nested: ['onFocus', 'onBlur', 'onMouseenter', 'onMouseleave', 'onClick'] }; function appendEvents(vNode, trigger, events) { triggerEventMap[trigger].forEach(eventName => { if (!vNode.props) { vNode.props = {}; } else { vNode.props = Object.assign({}, vNode.props); } const originalHandler = vNode.props[eventName]; const handler = events[eventName]; if (!originalHandler) { vNode.props[eventName] = handler; } else { vNode.props[eventName] = (...args) => { originalHandler(...args); handler(...args); }; } }); } export const popoverBaseProps = { show: { type: Boolean, default: undefined }, defaultShow: Boolean, showArrow: { type: Boolean, default: true }, trigger: { type: String, default: 'hover' }, delay: { type: Number, default: 100 }, duration: { type: Number, default: 100 }, raw: Boolean, placement: { type: String, default: 'top' }, x: Number, y: Number, arrowPointToCenter: Boolean, disabled: Boolean, getDisabled: Function, displayDirective: { type: String, default: 'if' }, arrowClass: String, arrowStyle: [String, Object], arrowWrapperClass: String, arrowWrapperStyle: [String, Object], flip: { type: Boolean, default: true }, animated: { type: Boolean, default: true }, width: { type: [Number, String], default: undefined }, overlap: Boolean, keepAliveOnHover: { type: Boolean, default: true }, zIndex: Number, to: useAdjustedTo.propTo, scrollable: Boolean, contentClass: String, contentStyle: [Object, String], headerClass: String, headerStyle: [Object, String], footerClass: String, footerStyle: [Object, String], // events onClickoutside: Function, 'onUpdate:show': [Function, Array], onUpdateShow: [Function, Array], // internal internalDeactivateImmediately: Boolean, internalSyncTargetWithParent: Boolean, internalInheritedEventHandlers: { type: Array, default: () => [] }, internalTrapFocus: Boolean, internalExtraClass: { type: Array, default: () => [] }, // deprecated onShow: [Function, Array], onHide: [Function, Array], arrow: { type: Boolean, default: undefined }, minWidth: Number, maxWidth: Number }; export const popoverProps = Object.assign(Object.assign(Object.assign({}, useTheme.props), popoverBaseProps), { internalOnAfterLeave: Function, internalRenderBody: Function }); export default defineComponent({ name: 'Popover', inheritAttrs: false, props: popoverProps, slots: Object, __popover__: true, setup(props) { if (process.env.NODE_ENV !== 'production') { watchEffect(() => { if (props.maxWidth !== undefined) { warnOnce('popover', '`max-width` is deprecated, please use `style` instead.'); } if (props.minWidth !== undefined) { warnOnce('popover', '`min-width` is deprecated, please use `style` instead.'); } if (props.arrow !== undefined) { warnOnce('popover', '`arrow` is deprecated, please use `showArrow` instead.'); } if (props.onHide !== undefined) { warnOnce('popover', '`on-hide` is deprecated, please use `on-update:show` instead.'); } if (props.onShow !== undefined) { warnOnce('popover', '`on-show` is deprecated, please use `on-update:show` instead.'); } }); } const isMountedRef = useIsMounted(); const binderInstRef = ref(null); // setup show const controlledShowRef = computed(() => props.show); const uncontrolledShowRef = ref(props.defaultShow); const mergedShowWithoutDisabledRef = useMergedState(controlledShowRef, uncontrolledShowRef); const mergedShowConsideringDisabledPropRef = useMemo(() => { if (props.disabled) return false; return mergedShowWithoutDisabledRef.value; }); const getMergedDisabled = () => { if (props.disabled) return true; const { getDisabled } = props; if (getDisabled === null || getDisabled === void 0 ? void 0 : getDisabled()) return true; return false; }; const getMergedShow = () => { if (getMergedDisabled()) return false; return mergedShowWithoutDisabledRef.value; }; // setup show-arrow const compatibleShowArrowRef = useCompitable(props, ['arrow', 'showArrow']); const mergedShowArrowRef = computed(() => { if (props.overlap) return false; return compatibleShowArrowRef.value; }); // bodyInstance let bodyInstance = null; const showTimerIdRef = ref(null); const hideTimerIdRef = ref(null); const positionManuallyRef = useMemo(() => { return props.x !== undefined && props.y !== undefined; }); // methods function doUpdateShow(value) { const { 'onUpdate:show': _onUpdateShow, onUpdateShow, onShow, onHide } = props; uncontrolledShowRef.value = value; if (_onUpdateShow) { call(_onUpdateShow, value); } if (onUpdateShow) { call(onUpdateShow, value); } if (value && onShow) { call(onShow, true); } if (value && onHide) { call(onHide, false); } } function syncPosition() { if (bodyInstance) { bodyInstance.syncPosition(); } } function clearShowTimer() { const { value: showTimerId } = showTimerIdRef; if (showTimerId) { window.clearTimeout(showTimerId); showTimerIdRef.value = null; } } function clearHideTimer() { const { value: hideTimerId } = hideTimerIdRef; if (hideTimerId) { window.clearTimeout(hideTimerId); hideTimerIdRef.value = null; } } function handleFocus() { const mergedDisabled = getMergedDisabled(); if (props.trigger === 'focus' && !mergedDisabled) { if (getMergedShow()) return; doUpdateShow(true); } } function handleBlur() { const mergedDisabled = getMergedDisabled(); if (props.trigger === 'focus' && !mergedDisabled) { if (!getMergedShow()) return; doUpdateShow(false); } } function handleMouseEnter() { const mergedDisabled = getMergedDisabled(); if (props.trigger === 'hover' && !mergedDisabled) { clearHideTimer(); if (showTimerIdRef.value !== null) return; if (getMergedShow()) return; const delayCallback = () => { doUpdateShow(true); showTimerIdRef.value = null; }; const { delay } = props; if (delay === 0) { delayCallback(); } else { showTimerIdRef.value = window.setTimeout(delayCallback, delay); } } } function handleMouseLeave() { const mergedDisabled = getMergedDisabled(); if (props.trigger === 'hover' && !mergedDisabled) { clearShowTimer(); if (hideTimerIdRef.value !== null) return; if (!getMergedShow()) return; const delayedCallback = () => { doUpdateShow(false); hideTimerIdRef.value = null; }; const { duration } = props; if (duration === 0) { delayedCallback(); } else { hideTimerIdRef.value = window.setTimeout(delayedCallback, duration); } } } // will be called in popover-content function handleMouseMoveOutside() { handleMouseLeave(); } // will be called in popover-content function handleClickOutside(e) { var _a; if (!getMergedShow()) return; if (props.trigger === 'click') { clearShowTimer(); clearHideTimer(); doUpdateShow(false); } (_a = props.onClickoutside) === null || _a === void 0 ? void 0 : _a.call(props, e); } function handleClick() { if (props.trigger === 'click' && !getMergedDisabled()) { clearShowTimer(); clearHideTimer(); const nextShow = !getMergedShow(); doUpdateShow(nextShow); } } function handleKeydown(e) { if (!props.internalTrapFocus) return; if (e.key === 'Escape') { clearShowTimer(); clearHideTimer(); doUpdateShow(false); } } function setShow(value) { uncontrolledShowRef.value = value; } function getTriggerElement() { var _a; return (_a = binderInstRef.value) === null || _a === void 0 ? void 0 : _a.targetRef; } function setBodyInstance(value) { bodyInstance = value; } provide('NPopover', { getTriggerElement, handleKeydown, handleMouseEnter, handleMouseLeave, handleClickOutside, handleMouseMoveOutside, setBodyInstance, positionManuallyRef, isMountedRef, zIndexRef: toRef(props, 'zIndex'), extraClassRef: toRef(props, 'internalExtraClass'), internalRenderBodyRef: toRef(props, 'internalRenderBody') }); watchEffect(() => { if (mergedShowWithoutDisabledRef.value && getMergedDisabled()) { doUpdateShow(false); } }); const returned = { binderInstRef, positionManually: positionManuallyRef, mergedShowConsideringDisabledProp: mergedShowConsideringDisabledPropRef, // if to show popover body uncontrolledShow: uncontrolledShowRef, mergedShowArrow: mergedShowArrowRef, getMergedShow, setShow, handleClick, handleMouseEnter, handleMouseLeave, handleFocus, handleBlur, syncPosition }; return returned; }, render() { var _a; const { positionManually, $slots: slots } = this; let triggerVNode; let popoverInside = false; if (!positionManually) { triggerVNode = getFirstSlotVNode(slots, 'trigger'); if (triggerVNode) { triggerVNode = cloneVNode(triggerVNode); triggerVNode = triggerVNode.type === Text ? h('span', [triggerVNode]) : triggerVNode; const handlers = { onClick: this.handleClick, onMouseenter: this.handleMouseEnter, onMouseleave: this.handleMouseLeave, onFocus: this.handleFocus, onBlur: this.handleBlur }; if ((_a = triggerVNode.type) === null || _a === void 0 ? void 0 : _a.__popover__) { popoverInside = true; // We assume that there's no DOM event handlers on popover element if (!triggerVNode.props) { triggerVNode.props = { internalSyncTargetWithParent: true, internalInheritedEventHandlers: [] }; } triggerVNode.props.internalSyncTargetWithParent = true; if (!triggerVNode.props.internalInheritedEventHandlers) { triggerVNode.props.internalInheritedEventHandlers = [handlers]; } else { triggerVNode.props.internalInheritedEventHandlers = [handlers, ...triggerVNode.props.internalInheritedEventHandlers]; } } else { const { internalInheritedEventHandlers } = this; const ascendantAndCurrentHandlers = [handlers, ...internalInheritedEventHandlers]; const mergedHandlers = { onBlur: e => { ascendantAndCurrentHandlers.forEach(_handlers => { _handlers.onBlur(e); }); }, onFocus: e => { ascendantAndCurrentHandlers.forEach(_handlers => { _handlers.onFocus(e); }); }, onClick: e => { ascendantAndCurrentHandlers.forEach(_handlers => { _handlers.onClick(e); }); }, onMouseenter: e => { ascendantAndCurrentHandlers.forEach(_handlers => { _handlers.onMouseenter(e); }); }, onMouseleave: e => { ascendantAndCurrentHandlers.forEach(_handlers => { _handlers.onMouseleave(e); }); } }; appendEvents(triggerVNode, internalInheritedEventHandlers ? 'nested' : positionManually ? 'manual' : this.trigger, mergedHandlers); } } } return h(VBinder, { ref: "binderInstRef", syncTarget: !popoverInside, syncTargetWithParent: this.internalSyncTargetWithParent }, { default: () => { // We need to subscribe it. Sometimes rerender won't ge triggered. // `mergedShowConsideringDisabledProp` is not the final disabled status. // In ellpisis it's dynamic. void this.mergedShowConsideringDisabledProp; const mergedShow = this.getMergedShow(); return [this.internalTrapFocus && mergedShow ? withDirectives(h("div", { style: { position: 'fixed', top: 0, right: 0, bottom: 0, left: 0 } }), [[zindexable, { enabled: mergedShow, zIndex: this.zIndex }]]) : null, positionManually ? null : h(VTarget, null, { default: () => triggerVNode }), h(NPopoverBody, keep(this.$props, bodyPropKeys, Object.assign(Object.assign({}, this.$attrs), { showArrow: this.mergedShowArrow, show: mergedShow })), { default: () => { var _a, _b; return (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a); }, header: () => { var _a, _b; return (_b = (_a = this.$slots).header) === null || _b === void 0 ? void 0 : _b.call(_a); }, footer: () => { var _a, _b; return (_b = (_a = this.$slots).footer) === null || _b === void 0 ? void 0 : _b.call(_a); } })]; } }); } });