diff --git a/src/Image.tsx b/src/Image.tsx index 4e37d862..8a1235cf 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,6 +100,7 @@ const ImageInternal: CompoundedComponent = props => { // Image src: imgSrc, alt, + fetchPriority, placeholder, fallback, @@ -161,7 +163,10 @@ const ImageInternal: CompoundedComponent = props => { const imgCommonProps = useMemo( () => { - const obj: ImageElementProps = {}; + const obj: ImageElementProps = { + ...getFetchPriorityProps(fetchPriority), + }; + COMMON_PROPS.forEach((prop: any) => { if (props[prop] !== undefined) { obj[prop] = props[prop]; @@ -170,7 +175,7 @@ const ImageInternal: CompoundedComponent = props => { return obj; }, - COMMON_PROPS.map(prop => props[prop]), + [fetchPriority, ...COMMON_PROPS.map(prop => props[prop])], ); // ========================== Register ========================== 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) => { diff --git a/src/interface.ts b/src/interface.ts index 42120a73..c0185e29 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 */ @@ -13,7 +15,9 @@ export type ImageElementProps = Pick< | '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', () => { 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(