naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
371 lines • 13.3 kB
JavaScript
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();
}
});
}
});