|
|
|
@ -1,21 +1,29 @@
|
|
|
|
|
import type { ImgHTMLAttributes, CSSProperties, PropType } from 'vue'; |
|
|
|
|
import { ref, watch, defineComponent, computed, onMounted } from 'vue'; |
|
|
|
|
import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue'; |
|
|
|
|
import isNumber from 'lodash-es/isNumber'; |
|
|
|
|
import cn from '../../_util/classNames'; |
|
|
|
|
import PropTypes from '../../_util/vue-types'; |
|
|
|
|
import { getOffset } from '../../vc-util/Dom/css'; |
|
|
|
|
|
|
|
|
|
import type { MouseEventHandler } from './Preview'; |
|
|
|
|
import useMergedState from '../../_util/hooks/useMergedState'; |
|
|
|
|
import Preview from './Preview'; |
|
|
|
|
|
|
|
|
|
import type { MouseEventHandler } from '../../_util/EventInterface'; |
|
|
|
|
import PreviewGroup, { context } from './PreviewGroup'; |
|
|
|
|
|
|
|
|
|
import type { IDialogChildProps } from '../../vc-dialog/IDialogPropTypes'; |
|
|
|
|
export type GetContainer = string | HTMLElement | (() => HTMLElement); |
|
|
|
|
export interface ImagePreviewType { |
|
|
|
|
import type { PreviewProps } from './Preview'; |
|
|
|
|
|
|
|
|
|
export type ImagePreviewType = Omit< |
|
|
|
|
IDialogChildProps, |
|
|
|
|
'mask' | 'visible' | 'closable' | 'prefixCls' | 'onClose' | 'afterClose' | 'wrapClassName' |
|
|
|
|
> & { |
|
|
|
|
src?: string; |
|
|
|
|
visible?: boolean; |
|
|
|
|
onVisibleChange?: (value: boolean, prevValue: boolean) => void; |
|
|
|
|
getContainer?: GetContainer | false; |
|
|
|
|
} |
|
|
|
|
maskClassName?: string; |
|
|
|
|
icons?: PreviewProps['icons']; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export interface ImagePropsType extends Omit<ImgHTMLAttributes, 'placeholder' | 'onClick'> { |
|
|
|
|
// Original |
|
|
|
@ -27,11 +35,14 @@ export interface ImagePropsType extends Omit<ImgHTMLAttributes, 'placeholder' |
|
|
|
|
|
placeholder?: boolean; |
|
|
|
|
fallback?: string; |
|
|
|
|
preview?: boolean | ImagePreviewType; |
|
|
|
|
onClick?: MouseEventHandler; |
|
|
|
|
onError?: HTMLImageElement['onerror']; |
|
|
|
|
} |
|
|
|
|
export const imageProps = () => ({ |
|
|
|
|
src: String, |
|
|
|
|
wrapperClassName: String, |
|
|
|
|
wrapperStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, |
|
|
|
|
rootClassName: String, |
|
|
|
|
prefixCls: String, |
|
|
|
|
previewPrefixCls: String, |
|
|
|
|
placeholder: PropTypes.any, |
|
|
|
@ -40,10 +51,17 @@ export const imageProps = () => ({
|
|
|
|
|
type: [Boolean, Object] as PropType<boolean | ImagePreviewType>, |
|
|
|
|
default: true as boolean | ImagePreviewType, |
|
|
|
|
}, |
|
|
|
|
onClick: { |
|
|
|
|
type: Function as PropType<MouseEventHandler>, |
|
|
|
|
}, |
|
|
|
|
onError: { |
|
|
|
|
type: Function as PropType<HTMLImageElement['onerror']>, |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
|
type ImageStatus = 'normal' | 'error' | 'loading'; |
|
|
|
|
export type ImageProps = Partial<ReturnType<typeof imageProps>>; |
|
|
|
|
export type ImageStatus = 'normal' | 'error' | 'loading'; |
|
|
|
|
|
|
|
|
|
const mergeDefaultValue = <T extends object>(obj: T, defaultValues: object): T => { |
|
|
|
|
export const mergeDefaultValue = <T extends object>(obj: T, defaultValues: object): T => { |
|
|
|
|
const res = { ...obj }; |
|
|
|
|
Object.keys(defaultValues).forEach(key => { |
|
|
|
|
if (obj[key] === undefined) { |
|
|
|
@ -57,11 +75,11 @@ const ImageInternal = defineComponent({
|
|
|
|
|
name: 'Image', |
|
|
|
|
inheritAttrs: false, |
|
|
|
|
props: imageProps(), |
|
|
|
|
emits: ['click'], |
|
|
|
|
emits: ['click', 'error'], |
|
|
|
|
setup(props, { attrs, slots, emit }) { |
|
|
|
|
const prefixCls = computed(() => props.prefixCls); |
|
|
|
|
const previewPrefixCls = computed(() => `${prefixCls.value}-preview`); |
|
|
|
|
const preview = computed(() => { |
|
|
|
|
const preview = computed<ImagePreviewType>(() => { |
|
|
|
|
const defaultValues = { |
|
|
|
|
visible: undefined, |
|
|
|
|
onVisibleChange: () => {}, |
|
|
|
@ -75,16 +93,21 @@ const ImageInternal = defineComponent({
|
|
|
|
|
() => (props.placeholder && props.placeholder !== true) || slots.placeholder, |
|
|
|
|
); |
|
|
|
|
const previewVisible = computed(() => preview.value.visible); |
|
|
|
|
const onPreviewVisibleChange = computed(() => preview.value.onVisibleChange); |
|
|
|
|
const getPreviewContainer = computed(() => preview.value.getContainer); |
|
|
|
|
|
|
|
|
|
const isControlled = computed(() => previewVisible.value !== undefined); |
|
|
|
|
const isShowPreview = ref(!!previewVisible.value); |
|
|
|
|
watch(previewVisible, () => { |
|
|
|
|
isShowPreview.value = !!previewVisible.value; |
|
|
|
|
|
|
|
|
|
const onPreviewVisibleChange = (val, preval) => { |
|
|
|
|
preview.value.onVisibleChange?.(val, preval); |
|
|
|
|
}; |
|
|
|
|
const [isShowPreview, setShowPreview] = useMergedState(!!previewVisible.value, { |
|
|
|
|
value: previewVisible, |
|
|
|
|
onChange: onPreviewVisibleChange, |
|
|
|
|
}); |
|
|
|
|
watch(previewVisible, val => { |
|
|
|
|
setShowPreview(Boolean(val)); |
|
|
|
|
}); |
|
|
|
|
watch(isShowPreview, (val, preVal) => { |
|
|
|
|
onPreviewVisibleChange.value(val, preVal); |
|
|
|
|
onPreviewVisibleChange(val, preVal); |
|
|
|
|
}); |
|
|
|
|
const status = ref<ImageStatus>(isCustomPlaceholder.value ? 'loading' : 'normal'); |
|
|
|
|
watch( |
|
|
|
@ -108,13 +131,15 @@ const ImageInternal = defineComponent({
|
|
|
|
|
const onLoad = () => { |
|
|
|
|
status.value = 'normal'; |
|
|
|
|
}; |
|
|
|
|
const onError = () => { |
|
|
|
|
const onError = (e: Event) => { |
|
|
|
|
status.value = 'error'; |
|
|
|
|
emit('error', e); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onPreview: MouseEventHandler = e => { |
|
|
|
|
if (!isControlled.value) { |
|
|
|
|
const { left, top } = getOffset(e.target); |
|
|
|
|
|
|
|
|
|
if (isPreviewGroup.value) { |
|
|
|
|
setCurrent(currentId.value); |
|
|
|
|
setGroupMousePosition({ |
|
|
|
@ -131,13 +156,13 @@ const ImageInternal = defineComponent({
|
|
|
|
|
if (isPreviewGroup.value) { |
|
|
|
|
setGroupShowPreview(true); |
|
|
|
|
} else { |
|
|
|
|
isShowPreview.value = true; |
|
|
|
|
setShowPreview(true); |
|
|
|
|
} |
|
|
|
|
emit('click', e); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onPreviewClose = () => { |
|
|
|
|
isShowPreview.value = false; |
|
|
|
|
setShowPreview(false); |
|
|
|
|
if (!isControlled.value) { |
|
|
|
|
mousePosition.value = null; |
|
|
|
|
} |
|
|
|
@ -163,7 +188,7 @@ const ImageInternal = defineComponent({
|
|
|
|
|
return () => {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
unRegister = registerImage(currentId.value, props.src); |
|
|
|
|
unRegister = registerImage(currentId.value, props.src, canPreview.value); |
|
|
|
|
|
|
|
|
|
if (!canPreview.value) { |
|
|
|
|
unRegister(); |
|
|
|
@ -172,13 +197,21 @@ const ImageInternal = defineComponent({
|
|
|
|
|
{ flush: 'post', immediate: true }, |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
onUnmounted(unRegister); |
|
|
|
|
const toSizePx = (l: number | string) => { |
|
|
|
|
if (isNumber(l)) return l + 'px'; |
|
|
|
|
return l; |
|
|
|
|
}; |
|
|
|
|
return () => { |
|
|
|
|
const { prefixCls, wrapperClassName, fallback, src, preview, placeholder, wrapperStyle } = |
|
|
|
|
props; |
|
|
|
|
const { |
|
|
|
|
prefixCls, |
|
|
|
|
wrapperClassName, |
|
|
|
|
fallback, |
|
|
|
|
src: imgSrc, |
|
|
|
|
placeholder, |
|
|
|
|
wrapperStyle, |
|
|
|
|
rootClassName, |
|
|
|
|
} = props; |
|
|
|
|
const { |
|
|
|
|
width, |
|
|
|
|
height, |
|
|
|
@ -191,11 +224,12 @@ const ImageInternal = defineComponent({
|
|
|
|
|
class: cls, |
|
|
|
|
style, |
|
|
|
|
} = attrs as ImgHTMLAttributes; |
|
|
|
|
const wrappperClass = cn(prefixCls, wrapperClassName, { |
|
|
|
|
const { icons, maskClassName, src: previewSrc, ...dialogProps } = preview.value; |
|
|
|
|
|
|
|
|
|
const wrappperClass = cn(prefixCls, wrapperClassName, rootClassName, { |
|
|
|
|
[`${prefixCls}-error`]: isError.value, |
|
|
|
|
}); |
|
|
|
|
const mergedSrc = isError.value && fallback ? fallback : src; |
|
|
|
|
const previewMask = slots.previewMask && slots.previewMask(); |
|
|
|
|
const mergedSrc = isError.value && fallback ? fallback : previewSrc ?? imgSrc; |
|
|
|
|
const imgCommonProps = { |
|
|
|
|
crossorigin, |
|
|
|
|
decoding, |
|
|
|
@ -215,12 +249,13 @@ const ImageInternal = defineComponent({
|
|
|
|
|
...(style as CSSProperties), |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<div |
|
|
|
|
class={wrappperClass} |
|
|
|
|
onClick={ |
|
|
|
|
preview && !isError.value |
|
|
|
|
canPreview.value |
|
|
|
|
? onPreview |
|
|
|
|
: e => { |
|
|
|
|
emit('click', e); |
|
|
|
@ -238,7 +273,7 @@ const ImageInternal = defineComponent({
|
|
|
|
|
? { |
|
|
|
|
src: fallback, |
|
|
|
|
} |
|
|
|
|
: { onLoad, onError, src })} |
|
|
|
|
: { onLoad, onError, src: imgSrc })} |
|
|
|
|
ref={img} |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
@ -248,12 +283,13 @@ const ImageInternal = defineComponent({
|
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
{/* Preview Click Mask */} |
|
|
|
|
{previewMask && canPreview.value && ( |
|
|
|
|
<div class={`${prefixCls}-mask`}>{previewMask}</div> |
|
|
|
|
{slots.previewMask && canPreview.value && ( |
|
|
|
|
<div class={[`${prefixCls}-mask`, maskClassName]}>{slots.previewMask()}</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
{!isPreviewGroup.value && canPreview.value && ( |
|
|
|
|
<Preview |
|
|
|
|
{...dialogProps} |
|
|
|
|
aria-hidden={!isShowPreview.value} |
|
|
|
|
visible={isShowPreview.value} |
|
|
|
|
prefixCls={previewPrefixCls.value} |
|
|
|
@ -262,6 +298,8 @@ const ImageInternal = defineComponent({
|
|
|
|
|
src={mergedSrc} |
|
|
|
|
alt={alt} |
|
|
|
|
getContainer={getPreviewContainer.value} |
|
|
|
|
icons={icons} |
|
|
|
|
rootClassName={rootClassName} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
</> |
|
|
|
|