diff --git a/src/Image.tsx b/src/Image.tsx
index 0d08140..b884d06 100644
--- a/src/Image.tsx
+++ b/src/Image.tsx
@@ -5,7 +5,10 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { GetContainer } from 'rc-util/lib/PortalWrapper';
import * as React from 'react';
import { useContext, useEffect, useRef, useState } from 'react';
+import { PreviewGroupContext } from './hooks/context';
import type { TransformType } from './hooks/useImageTransform';
+import useRegisterImage from './hooks/useRegisterImage';
+import { ImageElementProps } from './interface';
import type { PreviewProps, ToolbarRenderType } from './Preview';
import Preview from './Preview';
import PreviewGroup, { context } from './PreviewGroup';
@@ -61,6 +64,18 @@ interface CompoundedComponent
extends React.FC
{
type ImageStatus = 'normal' | 'error' | 'loading';
+const COMMON_PROPS: (keyof ImageElementProps)[] = [
+ 'crossOrigin',
+ 'decoding',
+ 'draggable',
+ 'loading',
+ 'referrerPolicy',
+ 'sizes',
+ 'srcSet',
+ 'useMap',
+ 'alt',
+];
+
function isImageValid(src) {
return new Promise(resolve => {
const img = document.createElement('img');
@@ -70,36 +85,29 @@ function isImageValid(src) {
});
}
-const ImageInternal: CompoundedComponent = ({
- src: imgSrc,
- alt,
- onPreviewClose: onInitialPreviewClose,
- prefixCls = 'rc-image',
- previewPrefixCls = `${prefixCls}-preview`,
- placeholder,
- fallback,
- width,
- height,
- style,
- preview = true,
- className,
- onClick,
- onError,
- wrapperClassName,
- wrapperStyle,
- rootClassName,
-
- // Img
- crossOrigin,
- decoding,
- loading,
- referrerPolicy,
- sizes,
- srcSet,
- useMap,
- draggable,
- ...otherProps
-}) => {
+const ImageInternal: CompoundedComponent = props => {
+ const {
+ src: imgSrc,
+ alt,
+ onPreviewClose: onInitialPreviewClose,
+ prefixCls = 'rc-image',
+ previewPrefixCls = `${prefixCls}-preview`,
+ placeholder,
+ fallback,
+ width,
+ height,
+ style,
+ preview = true,
+ className,
+ onClick,
+ onError,
+ wrapperClassName,
+ wrapperStyle,
+ rootClassName,
+
+ ...otherProps
+ } = props;
+
const isCustomPlaceholder = placeholder && placeholder !== true;
const {
src: previewSrc,
@@ -128,10 +136,12 @@ const ImageInternal: CompoundedComponent = ({
isPreviewGroup,
setShowPreview: setGroupShowPreview,
setMousePosition: setGroupMousePosition,
- registerImage,
setCurrentIndex,
getStartPreviewIndex,
} = useContext(context);
+
+ const groupContext = useContext(PreviewGroupContext);
+
const [currentId] = useState(() => {
uuid += 1;
return uuid;
@@ -140,43 +150,10 @@ const ImageInternal: CompoundedComponent = ({
const isLoaded = useRef(false);
- const imgCommonProps = {
- crossOrigin,
- decoding,
- draggable,
- loading,
- referrerPolicy,
- sizes,
- srcSet,
- useMap,
- alt,
- };
-
const onLoad = () => {
setStatus('normal');
};
- const onPreview: React.MouseEventHandler = e => {
- const { left, top } = getOffset(e.target);
- if (isPreviewGroup) {
- setGroupMousePosition({
- x: left,
- y: top,
- });
-
- setCurrentIndex(getStartPreviewIndex(currentId));
- setGroupShowPreview(true);
- } else {
- setMousePosition({
- x: left,
- y: top,
- });
- setShowPreview(true);
- }
-
- onClick?.(e);
- };
-
const onPreviewClose = () => {
setShowPreview(false);
setMousePosition(null);
@@ -199,23 +176,23 @@ const ImageInternal: CompoundedComponent = ({
});
}, [src]);
- // Keep order start
- // Resolve https://github.com/ant-design/ant-design/issues/28881
- // Only need unRegister when component unMount
- useEffect(() => {
- const unRegister = registerImage(currentId, {
- src,
- imgCommonProps,
- canPreview,
- });
+ // // Keep order start
+ // // Resolve https://github.com/ant-design/ant-design/issues/28881
+ // // Only need unRegister when component unMount
+ // useEffect(() => {
+ // const unRegister = registerImage(currentId, {
+ // src,
+ // imgCommonProps,
+ // canPreview,
+ // });
- return unRegister;
- }, []);
+ // return unRegister;
+ // }, []);
- useEffect(() => {
- registerImage(currentId, { src, imgCommonProps, canPreview });
- }, [src, canPreview, JSON.stringify(imgCommonProps)]);
- // Keep order end
+ // useEffect(() => {
+ // registerImage(currentId, { src, imgCommonProps, canPreview });
+ // }, [src, canPreview, JSON.stringify(imgCommonProps)]);
+ // // Keep order end
useEffect(() => {
if (isError) {
@@ -232,6 +209,57 @@ const ImageInternal: CompoundedComponent = ({
const mergedSrc = isError && fallback ? fallback : src;
+ // ========================= ImageProps =========================
+ const imgCommonProps = React.useMemo(
+ () => {
+ const obj: ImageElementProps = {};
+ COMMON_PROPS.forEach((prop: any) => {
+ if (props[prop] !== undefined) {
+ obj[prop] = props[prop];
+ }
+ });
+
+ return obj;
+ },
+ COMMON_PROPS.map(prop => props[prop]),
+ );
+
+ // ========================== Register ==========================
+ const registerData: ImageElementProps = React.useMemo(
+ () => ({
+ ...imgCommonProps,
+ src,
+ }),
+ [src, imgCommonProps],
+ );
+
+ const imageId = useRegisterImage(canPreview, registerData);
+
+ // ========================== Preview ===========================
+ const onPreview: React.MouseEventHandler = e => {
+ const { left, top } = getOffset(e.target);
+ if (groupContext) {
+ // setGroupMousePosition({
+ // x: left,
+ // y: top,
+ // });
+
+ // setCurrentIndex(getStartPreviewIndex(currentId));
+ // setGroupShowPreview(true);
+
+ groupContext.onPreview(imageId, left, top);
+ } else {
+ setMousePosition({
+ x: left,
+ y: top,
+ });
+ setShowPreview(true);
+ }
+
+ onClick?.(e);
+ };
+
+ // =========================== Render ===========================
return (
<>
{
flipX?: React.ReactNode;
flipY?: React.ReactNode;
};
+ count?: number;
countRender?: (current: number, total: number) => string;
scaleStep?: number;
minScale?: number;
@@ -78,6 +79,7 @@ const Preview: React.FC
= props => {
icons = {},
rootClassName,
getContainer,
+ count = 1,
countRender,
scaleStep = 0.5,
minScale = 1,
@@ -100,7 +102,7 @@ const Preview: React.FC = props => {
transformY: 0,
});
const [isMoving, setMoving] = useState(false);
- const { count, currentIndex, isPreviewGroup, setCurrentIndex } = useContext(context);
+ const { currentIndex, isPreviewGroup, setCurrentIndex } = useContext(context);
const showLeftOrRightSwitches = isPreviewGroup && count > 1;
const showOperationsProgress = isPreviewGroup && count >= 1;
const { transform, resetTransform, updateTransform, dispatchZoomChange } = useImageTransform(
diff --git a/src/PreviewGroup.tsx b/src/PreviewGroup.tsx
index f400d68..e692d47 100644
--- a/src/PreviewGroup.tsx
+++ b/src/PreviewGroup.tsx
@@ -1,9 +1,12 @@
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react';
import { useEffect, useState } from 'react';
+import { PreviewGroupContext } from './hooks/context';
import type { TransformType } from './hooks/useImageTransform';
import usePreviewInfo from './hooks/usePreviewInfo';
+import usePreviewItems from './hooks/usePreviewItems';
import type { ImagePreviewType } from './Image';
+import { ImageElementProps, OnGroupPreview } from './interface';
import type { PreviewProps, ToolbarRenderType } from './Preview';
import Preview from './Preview';
@@ -28,22 +31,10 @@ export interface PreviewGroupPreview
onChange?: (current: number, prevCurrent: number) => void;
}
-type PickImgHTMLAttributes =
- | 'src'
- | 'crossOrigin'
- | 'alt'
- | 'decoding'
- | 'draggable'
- | 'loading'
- | 'referrerPolicy'
- | 'sizes'
- | 'srcSet'
- | 'useMap';
-
export interface GroupConsumerProps {
previewPrefixCls?: string;
icons?: PreviewProps['icons'];
- items?: (string | Pick, PickImgHTMLAttributes>)[];
+ items?: (string | ImageElementProps)[];
preview?: boolean | PreviewGroupPreview;
children?: React.ReactNode;
}
@@ -56,13 +47,11 @@ export interface PreviewData {
export interface GroupConsumerValue extends GroupConsumerProps {
isPreviewGroup?: boolean;
- count: number;
currentIndex: number;
getStartPreviewIndex: (currentId: number) => number;
setCurrentIndex: React.Dispatch>;
setShowPreview: React.Dispatch>;
setMousePosition: React.Dispatch>;
- registerImage: (id: number, data: PreviewData) => () => void;
rootClassName?: string;
}
@@ -71,10 +60,8 @@ export const context = React.createContext({
currentIndex: null,
getStartPreviewIndex: () => null,
setCurrentIndex: () => null,
- count: null,
setShowPreview: () => null,
setMousePosition: () => null,
- registerImage: () => () => null,
rootClassName: '',
});
@@ -102,10 +89,17 @@ const Group: React.FC = ({
...dialogProps
} = typeof preview === 'object' ? preview : ({} as PreviewGroupPreview);
+ // ========================== Items ===========================
+ const [mergedItems, register] = usePreviewItems(items);
+
+ // ========================= Preview ==========================
+ // >>> Index
const [currentIndex, setCurrentIndex] = useMergedState(0, {
value: current,
});
+ const src = mergedItems[currentIndex]?.src;
+ // >>> Visible
const [isShowPreview, setShowPreview] = useMergedState(!!previewVisible, {
value: previewVisible,
onChange: (val, prevVal) => {
@@ -113,9 +107,21 @@ const Group: React.FC = ({
},
});
+ // >>> Position
const [mousePosition, setMousePosition] = useState(null);
- const { count, src, imgCommonProps, registerImage, getStartPreviewIndex } = usePreviewInfo({
+ const onPreviewFromImage = React.useCallback(
+ (id, mouseX, mouseY) => {
+ setShowPreview(true);
+ setMousePosition({ x: mouseX, y: mouseY });
+ setCurrentIndex(mergedItems.findIndex(item => item.id === id));
+ },
+ [mergedItems],
+ );
+
+ // ========================== Legacy ==========================
+
+ const { imgCommonProps, registerImage, getStartPreviewIndex } = usePreviewInfo({
items,
currentIndex,
});
@@ -134,40 +140,48 @@ const Group: React.FC = ({
}
}, [isShowPreview, isControlledCurrent]);
+ // ========================= Context ==========================
+ const previewGroupContext = React.useMemo(
+ () => ({ register, onPreview: onPreviewFromImage }),
+ [register, onPreviewFromImage],
+ );
+
+ // ========================== Render ==========================
return (
-
- {children}
-
-
+
+
+ {children}
+
+
+
);
};
diff --git a/src/hooks/context.ts b/src/hooks/context.ts
new file mode 100644
index 0000000..5a26f9e
--- /dev/null
+++ b/src/hooks/context.ts
@@ -0,0 +1,9 @@
+import * as React from 'react';
+import type { OnGroupPreview, RegisterImage } from '../interface';
+
+export interface PreviewGroupContextProps {
+ register: RegisterImage;
+ onPreview: OnGroupPreview;
+}
+
+export const PreviewGroupContext = React.createContext(null);
diff --git a/src/hooks/usePreviewItems.ts b/src/hooks/usePreviewItems.ts
new file mode 100644
index 0000000..83dea0f
--- /dev/null
+++ b/src/hooks/usePreviewItems.ts
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import type { PreviewImageElementProps, RegisterImage } from '../interface';
+import type { GroupConsumerProps } from '../PreviewGroup';
+
+export type Items = (PreviewImageElementProps & {
+ id: number;
+})[];
+
+/**
+ * Merge props provided `items` or context collected images
+ */
+export default function usePreviewItems(
+ items?: GroupConsumerProps['items'],
+): [items: Items, registerImage: RegisterImage] {
+ // Context collection image data
+ const [images, setImages] = React.useState>({});
+
+ const registerImage = React.useCallback((id, data) => {
+ setImages(imgs => ({
+ ...imgs,
+ [id]: data,
+ }));
+
+ return () => {
+ setImages(imgs => {
+ const cloneImgs = { ...imgs };
+ delete cloneImgs[id];
+ return cloneImgs;
+ });
+ };
+ }, []);
+
+ // items
+ const mergedItems = React.useMemo(() => {
+ if (items) {
+ return items.map((item, index) => {
+ const itemObj = typeof item === 'string' ? { src: item } : item;
+
+ return {
+ ...itemObj,
+ id: index,
+ };
+ });
+ }
+
+ return Object.keys(images).map(id => ({
+ ...images[id],
+ id: Number(id),
+ }));
+ }, [images]);
+
+ return [mergedItems, registerImage];
+}
diff --git a/src/hooks/useRegisterImage.ts b/src/hooks/useRegisterImage.ts
new file mode 100644
index 0000000..aef18a7
--- /dev/null
+++ b/src/hooks/useRegisterImage.ts
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import type { ImageElementProps } from '../interface';
+import { PreviewGroupContext } from './context';
+
+let uid = 0;
+
+export default function useRegisterImage(canPreview: boolean, imgData: ImageElementProps) {
+ const [id] = React.useState(() => {
+ uid += 1;
+ return uid;
+ });
+ const groupContext = React.useContext(PreviewGroupContext);
+
+ const registerData = {
+ ...imgData,
+ canPreview,
+ };
+
+ // Keep order start
+ // Resolve https://github.com/ant-design/ant-design/issues/28881
+ // Only need unRegister when component unMount
+ React.useEffect(() => {
+ if (groupContext) {
+ return groupContext.register(id, registerData);
+ }
+ }, [canPreview]);
+
+ React.useEffect(() => {
+ if (groupContext) {
+ return groupContext.register(id, registerData);
+ }
+ }, [imgData]);
+
+ return id;
+}
diff --git a/src/interface.ts b/src/interface.ts
new file mode 100644
index 0000000..a10c399
--- /dev/null
+++ b/src/interface.ts
@@ -0,0 +1,26 @@
+import * as React from 'react';
+
+/**
+ * Used for PreviewGroup passed image data
+ */
+export type ImageElementProps = Pick<
+ React.ImgHTMLAttributes,
+ | 'src'
+ | 'crossOrigin'
+ | 'decoding'
+ | 'draggable'
+ | 'loading'
+ | 'referrerPolicy'
+ | 'sizes'
+ | 'srcSet'
+ | 'useMap'
+ | 'alt'
+>;
+
+export type PreviewImageElementProps = ImageElementProps & {
+ canPreview: boolean;
+};
+
+export type RegisterImage = (id: number, data: PreviewImageElementProps) => VoidFunction;
+
+export type OnGroupPreview = (id: number, mouseX: number, mouseY: number) => void;