UNPKG

naive-ui

Version:

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

371 lines 13.3 kB
import { Fragment, Transition, computed, defineComponent, h, inject, mergeProps, onBeforeUnmount, provide, ref, toRef, vShow, watch, watchEffect, withDirectives } from 'vue'; import { VFocusTrap, VFollower } from 'vueuc'; import { clickoutside, mousemoveoutside } from 'vdirs'; import { getPreciseEventTarget } from 'seemly'; import { NxScrollbar } from "../../_internal/scrollbar/index.mjs"; import { drawerBodyInjectionKey } from "../../drawer/src/interface.mjs"; import { modalBodyInjectionKey } from "../../modal/src/interface.mjs"; import { useConfig, useTheme, useThemeClass } from "../../_mixins/index.mjs"; import { formatLength, isJsdom, isSlotEmpty, resolveWrappedSlot, useAdjustedTo } from "../../_utils/index.mjs"; import { popoverLight } from "../styles/index.mjs"; import { popoverBodyInjectionKey } from "./interface.mjs"; import style from "./styles/index.cssr.mjs"; export const popoverBodyProps = Object.assign(Object.assign({}, useTheme.props), { to: useAdjustedTo.propTo, show: Boolean, trigger: String, showArrow: Boolean, delay: Number, duration: Number, raw: Boolean, arrowPointToCenter: Boolean, arrowClass: String, arrowStyle: [String, Object], arrowWrapperClass: String, arrowWrapperStyle: [String, Object], displayDirective: String, x: Number, y: Number, flip: Boolean, overlap: Boolean, placement: String, width: [Number, String], keepAliveOnHover: Boolean, scrollable: Boolean, contentClass: String, contentStyle: [Object, String], headerClass: String, headerStyle: [Object, String], footerClass: String, footerStyle: [Object, String], // private internalDeactivateImmediately: Boolean, animated: Boolean, onClickoutside: Function, internalTrapFocus: Boolean, internalOnAfterLeave: Function, // deprecated minWidth: Number, maxWidth: Number }); export function renderArrow({ arrowClass, arrowStyle, arrowWrapperClass, arrowWrapperStyle, clsPrefix }) { return h("div", { key: "__popover-arrow__", style: arrowWrapperStyle, class: [`${clsPrefix}-popover-arrow-wrapper`, arrowWrapperClass] }, h("div", { class: [`${clsPrefix}-popover-arrow`, arrowClass], style: arrowStyle })); } export default defineComponent({ name: 'PopoverBody', inheritAttrs: false, props: popoverBodyProps, setup(props, { slots, attrs }) { const { namespaceRef, mergedClsPrefixRef, inlineThemeDisabled } = useConfig(props); const themeRef = useTheme('Popover', '-popover', style, popoverLight, props, mergedClsPrefixRef); const followerRef = ref(null); const NPopover = inject('NPopover'); const bodyRef = ref(null); const followerEnabledRef = ref(props.show); const displayedRef = ref(false); watchEffect(() => { const { show } = props; if (show && !isJsdom() && !props.internalDeactivateImmediately) { displayedRef.value = true; } }); const directivesRef = computed(() => { const { trigger, onClickoutside } = props; const directives = []; const { positionManuallyRef: { value: positionManually } } = NPopover; if (!positionManually) { if (trigger === 'click' && !onClickoutside) { directives.push([clickoutside, handleClickOutside, undefined, { capture: true }]); } if (trigger === 'hover') { directives.push([mousemoveoutside, handleMouseMoveOutside]); } } if (onClickoutside) { directives.push([clickoutside, handleClickOutside, undefined, { capture: true }]); } if (props.displayDirective === 'show' || props.animated && displayedRef.value) { directives.push([vShow, props.show]); } return directives; }); const cssVarsRef = computed(() => { const { common: { cubicBezierEaseInOut, cubicBezierEaseIn, cubicBezierEaseOut }, self: { space, spaceArrow, padding, fontSize, textColor, dividerColor, color, boxShadow, borderRadius, arrowHeight, arrowOffset, arrowOffsetVertical } } = themeRef.value; return { '--n-box-shadow': boxShadow, '--n-bezier': cubicBezierEaseInOut, '--n-bezier-ease-in': cubicBezierEaseIn, '--n-bezier-ease-out': cubicBezierEaseOut, '--n-font-size': fontSize, '--n-text-color': textColor, '--n-color': color, '--n-divider-color': dividerColor, '--n-border-radius': borderRadius, '--n-arrow-height': arrowHeight, '--n-arrow-offset': arrowOffset, '--n-arrow-offset-vertical': arrowOffsetVertical, '--n-padding': padding, '--n-space': space, '--n-space-arrow': spaceArrow }; }); const styleRef = computed(() => { const width = props.width === 'trigger' ? undefined : formatLength(props.width); const style = []; if (width) { style.push({ width }); } const { maxWidth, minWidth } = props; if (maxWidth) { style.push({ maxWidth: formatLength(maxWidth) }); } if (minWidth) { style.push({ maxWidth: formatLength(minWidth) }); } if (!inlineThemeDisabled) { style.push(cssVarsRef.value); } return style; }); const themeClassHandle = inlineThemeDisabled ? useThemeClass('popover', undefined, cssVarsRef, props) : undefined; NPopover.setBodyInstance({ syncPosition }); onBeforeUnmount(() => { NPopover.setBodyInstance(null); }); watch(toRef(props, 'show'), value => { // If no animation, no transition component will be applied to the // component. So we need to trigger follower manaully. if (props.animated) return; if (value) { followerEnabledRef.value = true; } else { followerEnabledRef.value = false; } }); function syncPosition() { var _a; (_a = followerRef.value) === null || _a === void 0 ? void 0 : _a.syncPosition(); } function handleMouseEnter(e) { if (props.trigger === 'hover' && props.keepAliveOnHover && props.show) { NPopover.handleMouseEnter(e); } } function handleMouseLeave(e) { if (props.trigger === 'hover' && props.keepAliveOnHover) { NPopover.handleMouseLeave(e); } } function handleMouseMoveOutside(e) { if (props.trigger === 'hover' && !getTriggerElement().contains(getPreciseEventTarget(e))) { NPopover.handleMouseMoveOutside(e); } } function handleClickOutside(e) { if (props.trigger === 'click' && !getTriggerElement().contains(getPreciseEventTarget(e)) || props.onClickoutside) { NPopover.handleClickOutside(e); } } function getTriggerElement() { return NPopover.getTriggerElement(); } provide(popoverBodyInjectionKey, bodyRef); provide(drawerBodyInjectionKey, null); provide(modalBodyInjectionKey, null); function renderContentNode() { themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender(); const shouldRenderDom = props.displayDirective === 'show' || props.show || props.animated && displayedRef.value; if (!shouldRenderDom) { return null; } let contentNode; const renderBody = NPopover.internalRenderBodyRef.value; const { value: mergedClsPrefix } = mergedClsPrefixRef; if (!renderBody) { const { value: extraClass } = NPopover.extraClassRef; const { internalTrapFocus } = props; const hasHeaderOrFooter = !isSlotEmpty(slots.header) || !isSlotEmpty(slots.footer); const renderContentInnerNode = () => { var _a, _b; const body = hasHeaderOrFooter ? h(Fragment, null, resolveWrappedSlot(slots.header, children => { return children ? h("div", { class: [`${mergedClsPrefix}-popover__header`, props.headerClass], style: props.headerStyle }, children) : null; }), resolveWrappedSlot(slots.default, children => { return children ? h("div", { class: [`${mergedClsPrefix}-popover__content`, props.contentClass], style: props.contentStyle }, slots) : null; }), resolveWrappedSlot(slots.footer, children => { return children ? h("div", { class: [`${mergedClsPrefix}-popover__footer`, props.footerClass], style: props.footerStyle }, children) : null; })) : props.scrollable ? (_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots) : h("div", { class: [`${mergedClsPrefix}-popover__content`, props.contentClass], style: props.contentStyle }, slots); const maybeScrollableBody = props.scrollable ? h(NxScrollbar, { contentClass: hasHeaderOrFooter ? undefined : `${mergedClsPrefix}-popover__content ${(_b = props.contentClass) !== null && _b !== void 0 ? _b : ''}`, contentStyle: hasHeaderOrFooter ? undefined : props.contentStyle }, { default: () => body }) : body; const arrow = props.showArrow ? renderArrow({ arrowClass: props.arrowClass, arrowStyle: props.arrowStyle, arrowWrapperClass: props.arrowWrapperClass, arrowWrapperStyle: props.arrowWrapperStyle, clsPrefix: mergedClsPrefix }) : null; return [maybeScrollableBody, arrow]; }; contentNode = h('div', mergeProps({ class: [`${mergedClsPrefix}-popover`, `${mergedClsPrefix}-popover-shared`, themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass.value, extraClass.map(v => `${mergedClsPrefix}-${v}`), { [`${mergedClsPrefix}-popover--scrollable`]: props.scrollable, [`${mergedClsPrefix}-popover--show-header-or-footer`]: hasHeaderOrFooter, [`${mergedClsPrefix}-popover--raw`]: props.raw, [`${mergedClsPrefix}-popover-shared--overlap`]: props.overlap, [`${mergedClsPrefix}-popover-shared--show-arrow`]: props.showArrow, [`${mergedClsPrefix}-popover-shared--center-arrow`]: props.arrowPointToCenter }], ref: bodyRef, style: styleRef.value, onKeydown: NPopover.handleKeydown, onMouseenter: handleMouseEnter, onMouseleave: handleMouseLeave }, attrs), internalTrapFocus ? h(VFocusTrap, { active: props.show, autoFocus: true }, { default: renderContentInnerNode }) : renderContentInnerNode()); } else { contentNode = renderBody( // The popover class and overlap class must exists, they will be used // to place the body & transition animation. // Shadow class exists for reuse box-shadow. [`${mergedClsPrefix}-popover-shared`, themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass.value, props.overlap && `${mergedClsPrefix}-popover-shared--overlap`, props.showArrow && `${mergedClsPrefix}-popover-shared--show-arrow`, props.arrowPointToCenter && `${mergedClsPrefix}-popover-shared--center-arrow`], bodyRef, styleRef.value, handleMouseEnter, handleMouseLeave); } return withDirectives(contentNode, directivesRef.value); } return { displayed: displayedRef, namespace: namespaceRef, isMounted: NPopover.isMountedRef, zIndex: NPopover.zIndexRef, followerRef, adjustedTo: useAdjustedTo(props), followerEnabled: followerEnabledRef, renderContentNode }; }, render() { return h(VFollower, { ref: "followerRef", zIndex: this.zIndex, show: this.show, enabled: this.followerEnabled, to: this.adjustedTo, x: this.x, y: this.y, flip: this.flip, placement: this.placement, containerClass: this.namespace, overlap: this.overlap, width: this.width === 'trigger' ? 'target' : undefined, teleportDisabled: this.adjustedTo === useAdjustedTo.tdkey }, { default: () => { return this.animated ? h(Transition, { name: "popover-transition", appear: this.isMounted, // Don't use watch to enable follower, since the transition may // make position sync timing very subtle and buggy. onEnter: () => { this.followerEnabled = true; }, onAfterLeave: () => { var _a; (_a = this.internalOnAfterLeave) === null || _a === void 0 ? void 0 : _a.call(this); this.followerEnabled = false; this.displayed = false; } }, { default: this.renderContentNode }) : this.renderContentNode(); } }); } });