From 981714dd6130504fa7c58872a7654346cf9b5815 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:23:03 +0300 Subject: [PATCH 1/6] fix: support fetchPriority compatibility for React 18 and 19 --- src/Image.tsx | 34 +++++++++++++++++++++++++++--- src/Preview/index.tsx | 2 +- src/common.ts | 1 + src/interface.ts | 7 +++++- src/util.ts | 13 ++++++++++++ tests/fetchPriority.test.tsx | 41 ++++++++++++++++++++++++++++++++++++ tests/preview.test.tsx | 7 +++++- 7 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 tests/fetchPriority.test.tsx diff --git a/src/Image.tsx b/src/Image.tsx index 4e37d862..aef0ec89 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -11,6 +11,7 @@ import type { TransformType } from './hooks/useImageTransform'; import useRegisterImage from './hooks/useRegisterImage'; import useStatus from './hooks/useStatus'; import type { ImageElementProps } from './interface'; +import { getFetchPriorityProps } from './util'; export interface ImgInfo { url: string; @@ -99,8 +100,17 @@ const ImageInternal: CompoundedComponent = props => { // Image src: imgSrc, alt, + crossOrigin, + decoding, + draggable, + fetchPriority, + loading, placeholder, + referrerPolicy, fallback, + sizes, + srcSet, + useMap, // Preview preview = true, @@ -163,14 +173,32 @@ const ImageInternal: CompoundedComponent = props => { () => { const obj: ImageElementProps = {}; COMMON_PROPS.forEach((prop: any) => { - if (props[prop] !== undefined) { - obj[prop] = props[prop]; + if (prop === 'fetchPriority') { + Object.assign(obj, getFetchPriorityProps(fetchPriority)); + } else if (prop === 'crossOrigin' && crossOrigin !== undefined) { + obj.crossOrigin = crossOrigin; + } else if (prop === 'decoding' && decoding !== undefined) { + obj.decoding = decoding; + } else if (prop === 'draggable' && draggable !== undefined) { + obj.draggable = draggable; + } else if (prop === 'loading' && loading !== undefined) { + obj.loading = loading; + } else if (prop === 'referrerPolicy' && referrerPolicy !== undefined) { + obj.referrerPolicy = referrerPolicy; + } else if (prop === 'sizes' && sizes !== undefined) { + obj.sizes = sizes; + } else if (prop === 'srcSet' && srcSet !== undefined) { + obj.srcSet = srcSet; + } else if (prop === 'useMap' && useMap !== undefined) { + obj.useMap = useMap; + } else if (prop === 'alt' && alt !== undefined) { + obj.alt = alt; } }); return obj; }, - COMMON_PROPS.map(prop => props[prop]), + [alt, crossOrigin, decoding, draggable, fetchPriority, loading, referrerPolicy, sizes, srcSet, useMap], ); // ========================== Register ========================== diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 05594fca..1bdf9530 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -194,7 +194,7 @@ const Preview: React.FC = props => { zIndex, } = props; - const imgRef = useRef(); + const imgRef = useRef(null); const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1; diff --git a/src/common.ts b/src/common.ts index a52a58ae..8189e0d0 100644 --- a/src/common.ts +++ b/src/common.ts @@ -4,6 +4,7 @@ export const COMMON_PROPS: (keyof Omit)[] = [ 'crossOrigin', 'decoding', 'draggable', + 'fetchPriority', 'loading', 'referrerPolicy', 'sizes', diff --git a/src/interface.ts b/src/interface.ts index 42120a73..5d64cda2 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,3 +1,5 @@ +export type FetchPriority = 'high' | 'low' | 'auto'; + /** * Used for PreviewGroup passed image data */ @@ -7,13 +9,16 @@ export type ImageElementProps = Pick< | 'crossOrigin' | 'decoding' | 'draggable' + | 'fetchPriority' | 'loading' | 'referrerPolicy' | 'sizes' | 'srcSet' | 'useMap' | 'alt' ->; +> & { + fetchpriority?: FetchPriority; +}; export type PreviewImageElementProps = { data: ImageElementProps; diff --git a/src/util.ts b/src/util.ts index e0b77aee..49ef3639 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,6 @@ +import * as React from 'react'; +import type { FetchPriority } from './interface'; + export function isImageValid(src: string) { return new Promise(resolve => { if (!src) { @@ -11,6 +14,16 @@ export function isImageValid(src: string) { }); } +export function getFetchPriorityProps(value?: FetchPriority) { + if (!value) { + return {}; + } + + const major = Number(React.version.split('.')[0]); + + return major >= 19 ? { fetchPriority: value } : { fetchpriority: value }; +} + // ============================= Legacy ============================= export function getClientSize() { const width = document.documentElement.clientWidth; diff --git a/tests/fetchPriority.test.tsx b/tests/fetchPriority.test.tsx new file mode 100644 index 00000000..40c62887 --- /dev/null +++ b/tests/fetchPriority.test.tsx @@ -0,0 +1,41 @@ +describe('getFetchPriorityProps', () => { + afterEach(() => { + jest.resetModules(); + jest.dontMock('react'); + }); + + it('uses lowercase fetchpriority for React 18', () => { + jest.doMock('react', () => ({ + ...jest.requireActual('react'), + version: '18.3.1', + })); + + jest.isolateModules(() => { + const { getFetchPriorityProps } = require('../src/util'); + expect(getFetchPriorityProps('high')).toEqual({ + fetchpriority: 'high', + }); + }); + }); + + it('uses camelCase fetchPriority for React 19', () => { + jest.doMock('react', () => ({ + ...jest.requireActual('react'), + version: '19.2.4', + })); + + jest.isolateModules(() => { + const { getFetchPriorityProps } = require('../src/util'); + expect(getFetchPriorityProps('high')).toEqual({ + fetchPriority: 'high', + }); + }); + }); + + it('returns empty props when value is undefined', () => { + jest.isolateModules(() => { + const { getFetchPriorityProps } = require('../src/util'); + expect(getFetchPriorityProps()).toEqual({}); + }); + }); +}); diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index cebfe3f4..6f1dd43f 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -809,11 +809,12 @@ describe('Preview', () => { ); }); - it('pass img common props to previewed image', () => { + it('passes image common props to previewed image', () => { const { container } = render( , ); @@ -826,6 +827,10 @@ describe('Preview', () => { 'referrerPolicy', 'no-referrer', ); + expect(document.querySelector('.rc-image-preview-img')).toHaveAttribute( + 'fetchpriority', + 'high', + ); }); describe('actionsRender', () => { From 088f6f1a4293f2fdab3741500fe9c16704bc9805 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:41:32 +0300 Subject: [PATCH 2/6] refactor: simplify fetchPriority prop mapping --- src/Image.tsx | 27 ++++++--------------------- src/common.ts | 1 - 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Image.tsx b/src/Image.tsx index aef0ec89..11764887 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -171,28 +171,13 @@ const ImageInternal: CompoundedComponent = props => { const imgCommonProps = useMemo( () => { - const obj: ImageElementProps = {}; + const obj: ImageElementProps = { + ...getFetchPriorityProps(fetchPriority), + }; + COMMON_PROPS.forEach((prop: any) => { - if (prop === 'fetchPriority') { - Object.assign(obj, getFetchPriorityProps(fetchPriority)); - } else if (prop === 'crossOrigin' && crossOrigin !== undefined) { - obj.crossOrigin = crossOrigin; - } else if (prop === 'decoding' && decoding !== undefined) { - obj.decoding = decoding; - } else if (prop === 'draggable' && draggable !== undefined) { - obj.draggable = draggable; - } else if (prop === 'loading' && loading !== undefined) { - obj.loading = loading; - } else if (prop === 'referrerPolicy' && referrerPolicy !== undefined) { - obj.referrerPolicy = referrerPolicy; - } else if (prop === 'sizes' && sizes !== undefined) { - obj.sizes = sizes; - } else if (prop === 'srcSet' && srcSet !== undefined) { - obj.srcSet = srcSet; - } else if (prop === 'useMap' && useMap !== undefined) { - obj.useMap = useMap; - } else if (prop === 'alt' && alt !== undefined) { - obj.alt = alt; + if (props[prop] !== undefined) { + obj[prop] = props[prop]; } }); diff --git a/src/common.ts b/src/common.ts index 8189e0d0..a52a58ae 100644 --- a/src/common.ts +++ b/src/common.ts @@ -4,7 +4,6 @@ export const COMMON_PROPS: (keyof Omit)[] = [ 'crossOrigin', 'decoding', 'draggable', - 'fetchPriority', 'loading', 'referrerPolicy', 'sizes', From 3dc4c6bb8153ba656ba54cc0bec4004f943b33ce Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:47:12 +0300 Subject: [PATCH 3/6] refactor: minimize image prop handling diff --- src/Image.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Image.tsx b/src/Image.tsx index 11764887..8a1235cf 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -100,17 +100,9 @@ const ImageInternal: CompoundedComponent = props => { // Image src: imgSrc, alt, - crossOrigin, - decoding, - draggable, fetchPriority, - loading, placeholder, - referrerPolicy, fallback, - sizes, - srcSet, - useMap, // Preview preview = true, @@ -183,7 +175,7 @@ const ImageInternal: CompoundedComponent = props => { return obj; }, - [alt, crossOrigin, decoding, draggable, fetchPriority, loading, referrerPolicy, sizes, srcSet, useMap], + [fetchPriority, ...COMMON_PROPS.map(prop => props[prop])], ); // ========================== Register ========================== From fdfafacbd6ac54adb19b3cbb9ef59d9363ea51b3 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:50:13 +0300 Subject: [PATCH 4/6] refactor: narrow fetchPriority internal typing --- src/Preview/index.tsx | 2 +- src/interface.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 1bdf9530..05594fca 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -194,7 +194,7 @@ const Preview: React.FC = props => { zIndex, } = props; - const imgRef = useRef(null); + const imgRef = useRef(); const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1; diff --git a/src/interface.ts b/src/interface.ts index 5d64cda2..c0185e29 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -9,7 +9,6 @@ export type ImageElementProps = Pick< | 'crossOrigin' | 'decoding' | 'draggable' - | 'fetchPriority' | 'loading' | 'referrerPolicy' | 'sizes' From c3ec4bf9b36cfaae12ef68d230b9019717c5f109 Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:54:46 +0300 Subject: [PATCH 5/6] test: cover fetchPriority in preview group --- tests/previewGroup.test.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/previewGroup.test.tsx b/tests/previewGroup.test.tsx index 0c847edd..f368a687 100644 --- a/tests/previewGroup.test.tsx +++ b/tests/previewGroup.test.tsx @@ -281,6 +281,35 @@ describe('PreviewGroup', () => { ); }); + it('passes fetchpriority to previewed image in group mode', () => { + const { container } = render( + + + + , + ); + + fireEvent.click(container.querySelector('.rc-image')); + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview-img')).toHaveAttribute( + 'fetchpriority', + 'high', + ); + + fireEvent.click(document.querySelector('.rc-image-preview-switch-next')); + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('.rc-image-preview-img')).toHaveAttribute( + 'fetchpriority', + 'low', + ); + }); + it('album mode', () => { const { container } = render( From 5f3a75467eec34a1e89f8c7171f7379d7483963f Mon Sep 17 00:00:00 2001 From: User Date: Sun, 22 Mar 2026 15:56:04 +0300 Subject: [PATCH 6/6] fix: preserve fetchPriority in preview group --- src/PreviewGroup.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/PreviewGroup.tsx b/src/PreviewGroup.tsx index 8d3e9741..32c706c4 100644 --- a/src/PreviewGroup.tsx +++ b/src/PreviewGroup.tsx @@ -9,6 +9,7 @@ import { PreviewGroupContext } from './context'; import type { TransformType } from './hooks/useImageTransform'; import usePreviewItems from './hooks/usePreviewItems'; import type { ImageElementProps, OnGroupPreview } from './interface'; +import { getFetchPriorityProps } from './util'; export interface GroupPreviewConfig extends InternalPreviewConfig { current?: number; @@ -66,7 +67,15 @@ const Group: React.FC = ({ const [keepOpenIndex, setKeepOpenIndex] = useState(false); // >>> Image - const { src, ...imgCommonProps } = mergedItems[current]?.data || {}; + const { src, ...currentData } = mergedItems[current]?.data || {}; + const imgCommonProps = React.useMemo(() => { + const { fetchpriority, ...restImageProps } = currentData; + + return { + ...restImageProps, + ...getFetchPriorityProps(fetchpriority), + }; + }, [currentData]); // >>> Visible const [isShowPreview, setShowPreview] = useControlledState(!!previewOpen, previewOpen); const triggerShowPreview = useEvent((next: boolean) => { @@ -92,7 +101,7 @@ const Group: React.FC = ({ setKeepOpenIndex(true); }, - [mergedItems, fromItems], + [fromItems, mergedItems, setCurrent, triggerShowPreview], ); // Reset current when reopen @@ -104,7 +113,7 @@ const Group: React.FC = ({ } else { setKeepOpenIndex(false); } - }, [isShowPreview]); + }, [isShowPreview, keepOpenIndex, setCurrent]); // ========================== Events ========================== const onInternalChange: GroupPreviewConfig['onChange'] = (next, prev) => {