naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
293 lines • 9.52 kB
JavaScript
import { computed, defineComponent, h, inject, onBeforeUnmount, onMounted, ref, watch, watchEffect } from 'vue';
import { VResizeObserver } from 'vueuc';
import { useConfig, useTheme, useThemeClass } from "../../_mixins/index.mjs";
import { color2Class, createKey, resolveSlot, resolveWrappedSlot } from "../../_utils/index.mjs";
import { isImageSupportNativeLazy } from "../../_utils/env/is-native-lazy-load.mjs";
import { observeIntersection } from "../../image/src/utils.mjs";
import { tagInjectionKey } from "../../tag/src/Tag.mjs";
import { avatarLight } from "../styles/index.mjs";
import { avatarGroupInjectionKey } from "./context.mjs";
import style from "./styles/index.cssr.mjs";
export const avatarProps = Object.assign(Object.assign({}, useTheme.props), {
size: [String, Number],
src: String,
circle: {
type: Boolean,
default: undefined
},
objectFit: String,
round: {
type: Boolean,
default: undefined
},
bordered: {
type: Boolean,
default: undefined
},
onError: Function,
fallbackSrc: String,
intersectionObserverOptions: Object,
lazy: Boolean,
onLoad: Function,
renderPlaceholder: Function,
renderFallback: Function,
imgProps: Object,
/** @deprecated */
color: String
});
export default defineComponent({
name: 'Avatar',
props: avatarProps,
slots: Object,
setup(props) {
const {
mergedClsPrefixRef,
inlineThemeDisabled
} = useConfig(props);
const hasLoadErrorRef = ref(false);
let memoedTextHtml = null;
const textRef = ref(null);
const selfRef = ref(null);
const fitTextTransform = () => {
const {
value: textEl
} = textRef;
if (textEl) {
if (memoedTextHtml === null || memoedTextHtml !== textEl.innerHTML) {
memoedTextHtml = textEl.innerHTML;
const {
value: selfEl
} = selfRef;
if (selfEl) {
const {
offsetWidth: elWidth,
offsetHeight: elHeight
} = selfEl;
const {
offsetWidth: textWidth,
offsetHeight: textHeight
} = textEl;
const radix = 0.9;
const ratio = Math.min(elWidth / textWidth * radix, elHeight / textHeight * radix, 1);
textEl.style.transform = `translateX(-50%) translateY(-50%) scale(${ratio})`;
}
}
}
};
const NAvatarGroup = inject(avatarGroupInjectionKey, null);
const mergedSizeRef = computed(() => {
const {
size
} = props;
if (size) return size;
const {
size: avatarGroupSize
} = NAvatarGroup || {};
if (avatarGroupSize) return avatarGroupSize;
return 'medium';
});
const themeRef = useTheme('Avatar', '-avatar', style, avatarLight, props, mergedClsPrefixRef);
const TagInjection = inject(tagInjectionKey, null);
const mergedRoundRef = computed(() => {
if (NAvatarGroup) return true;
const {
round,
circle
} = props;
if (round !== undefined || circle !== undefined) return round || circle;
if (TagInjection) {
return TagInjection.roundRef.value;
}
return false;
});
const mergedBorderedRef = computed(() => {
if (NAvatarGroup) return true;
return props.bordered || false;
});
const cssVarsRef = computed(() => {
const size = mergedSizeRef.value;
const round = mergedRoundRef.value;
const bordered = mergedBorderedRef.value;
const {
color: propColor
} = props;
const {
self: {
borderRadius,
fontSize,
color,
border,
colorModal,
colorPopover
},
common: {
cubicBezierEaseInOut
}
} = themeRef.value;
let height;
if (typeof size === 'number') {
height = `${size}px`;
} else {
height = themeRef.value.self[createKey('height', size)];
}
return {
'--n-font-size': fontSize,
'--n-border': bordered ? border : 'none',
'--n-border-radius': round ? '50%' : borderRadius,
'--n-color': propColor || color,
'--n-color-modal': propColor || colorModal,
'--n-color-popover': propColor || colorPopover,
'--n-bezier': cubicBezierEaseInOut,
'--n-merged-size': `var(--n-avatar-size-override, ${height})`
};
});
const themeClassHandle = inlineThemeDisabled ? useThemeClass('avatar', computed(() => {
const size = mergedSizeRef.value;
const round = mergedRoundRef.value;
const bordered = mergedBorderedRef.value;
const {
color
} = props;
let hash = '';
if (size) {
if (typeof size === 'number') {
hash += `a${size}`;
} else {
hash += size[0];
}
}
if (round) {
hash += 'b';
}
if (bordered) {
hash += 'c';
}
if (color) {
hash += color2Class(color);
}
return hash;
}), cssVarsRef, props) : undefined;
const shouldStartLoadingRef = ref(!props.lazy);
onMounted(() => {
// Use IntersectionObserver if lazy and intersectionObserverOptions is set
if (props.lazy && props.intersectionObserverOptions) {
let unobserve;
const stopWatchHandle = watchEffect(() => {
unobserve === null || unobserve === void 0 ? void 0 : unobserve();
unobserve = undefined;
if (props.lazy) {
unobserve = observeIntersection(selfRef.value, props.intersectionObserverOptions, shouldStartLoadingRef);
}
});
onBeforeUnmount(() => {
stopWatchHandle();
unobserve === null || unobserve === void 0 ? void 0 : unobserve();
});
}
});
watch(() => {
var _a;
return props.src || ((_a = props.imgProps) === null || _a === void 0 ? void 0 : _a.src);
}, () => {
hasLoadErrorRef.value = false;
});
const loadedRef = ref(!props.lazy);
return {
textRef,
selfRef,
mergedRoundRef,
mergedClsPrefix: mergedClsPrefixRef,
fitTextTransform,
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender,
hasLoadError: hasLoadErrorRef,
shouldStartLoading: shouldStartLoadingRef,
loaded: loadedRef,
mergedOnError: e => {
if (!shouldStartLoadingRef.value) return;
hasLoadErrorRef.value = true;
const {
onError,
imgProps: {
onError: imgPropsOnError
} = {}
} = props;
onError === null || onError === void 0 ? void 0 : onError(e);
imgPropsOnError === null || imgPropsOnError === void 0 ? void 0 : imgPropsOnError(e);
},
mergedOnLoad: e => {
const {
onLoad,
imgProps: {
onLoad: imgPropsOnLoad
} = {}
} = props;
onLoad === null || onLoad === void 0 ? void 0 : onLoad(e);
imgPropsOnLoad === null || imgPropsOnLoad === void 0 ? void 0 : imgPropsOnLoad(e);
loadedRef.value = true;
}
};
},
render() {
var _a, _b;
const {
$slots,
src,
mergedClsPrefix,
lazy,
onRender,
loaded,
hasLoadError,
imgProps = {}
} = this;
onRender === null || onRender === void 0 ? void 0 : onRender();
let img;
const placeholderNode = !loaded && !hasLoadError && (this.renderPlaceholder ? this.renderPlaceholder() : (_b = (_a = this.$slots).placeholder) === null || _b === void 0 ? void 0 : _b.call(_a));
if (this.hasLoadError) {
img = this.renderFallback ? this.renderFallback() : resolveSlot($slots.fallback, () => [h("img", {
src: this.fallbackSrc,
style: {
objectFit: this.objectFit
}
})]);
} else {
img = resolveWrappedSlot($slots.default, children => {
if (children) {
return h(VResizeObserver, {
onResize: this.fitTextTransform
}, {
default: () => h("span", {
ref: "textRef",
class: `${mergedClsPrefix}-avatar__text`
}, children)
});
} else if (src || imgProps.src) {
const loadSrc = this.src || imgProps.src;
return h('img', Object.assign(Object.assign({}, imgProps), {
loading:
// If interseciton observer options is set, do not use native lazy
isImageSupportNativeLazy && !this.intersectionObserverOptions && lazy ? 'lazy' : 'eager',
src: lazy && this.intersectionObserverOptions ? this.shouldStartLoading ? loadSrc : undefined : loadSrc,
'data-image-src': loadSrc,
onLoad: this.mergedOnLoad,
onError: this.mergedOnError,
style: [imgProps.style || '', {
objectFit: this.objectFit
}, placeholderNode ? {
height: '0',
width: '0',
visibility: 'hidden',
position: 'absolute'
} : '']
}));
}
});
}
return h("span", {
ref: "selfRef",
class: [`${mergedClsPrefix}-avatar`, this.themeClass],
style: this.cssVars
}, img, lazy && placeholderNode);
}
});