From 7856ef84091c0ee7d132294efe45757882cc0ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Tue, 25 Feb 2025 09:12:52 -0800 Subject: [PATCH] Add Image and ImageBackground to buildTypes and align Flow with TS defs (#49659) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49659 Changelog: [Internal] - Added Image and ImageBackground to buildTypes and aligned Flow with TS defs Reviewed By: huntie Differential Revision: D70099485 fbshipit-source-id: 6000fc7099a4f811c451a08c4d5cf505d84ec0c9 --- .../Libraries/Image/Image.android.js | 2 +- .../Libraries/Image/Image.js.flow | 14 ++ .../Libraries/Image/ImageProps.js | 127 +++++++++++++----- .../Libraries/Image/ImageTypes.flow.js | 22 ++- .../__snapshots__/public-api-test.js.snap | 83 ++++++++---- .../types/__typetests__/index.tsx | 8 +- scripts/build/build-types/buildTypes.js | 2 + 7 files changed, 188 insertions(+), 70 deletions(-) diff --git a/packages/react-native/Libraries/Image/Image.android.js b/packages/react-native/Libraries/Image/Image.android.js index bbcea9b43c6..fa4f65243bd 100644 --- a/packages/react-native/Libraries/Image/Image.android.js +++ b/packages/react-native/Libraries/Image/Image.android.js @@ -138,7 +138,7 @@ let BaseImage: AbstractImageAndroid = React.forwardRef( props.loadingIndicatorSource, ); - if (props.children) { + if (props.children != null) { throw new Error( 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', ); diff --git a/packages/react-native/Libraries/Image/Image.js.flow b/packages/react-native/Libraries/Image/Image.js.flow index 2afd5d87ba2..ed1d07a664a 100644 --- a/packages/react-native/Libraries/Image/Image.js.flow +++ b/packages/react-native/Libraries/Image/Image.js.flow @@ -10,4 +10,18 @@ import type {Image} from './ImageTypes.flow'; +export type { + ImageProgressEventDataIOS, + ImagePropsIOS, + ImagePropsAndroid, + ImageSourcePropType, + ImageLoadEventData, + ImageErrorEventData, + ImagePropsBase, + ImageProps, + ImageBackgroundProps, +} from './ImageProps'; + +export type {ImageResolvedAssetSource, ImageSize} from './ImageTypes.flow'; + declare export default Image; diff --git a/packages/react-native/Libraries/Image/ImageProps.js b/packages/react-native/Libraries/Image/ImageProps.js index ab3c0c93617..a0216073a6f 100644 --- a/packages/react-native/Libraries/Image/ImageProps.js +++ b/packages/react-native/Libraries/Image/ImageProps.js @@ -23,20 +23,36 @@ import type { } from '../Types/CoreEventTypes'; import typeof Image from './Image'; import type {ImageResizeMode} from './ImageResizeMode'; -import type {ImageSource} from './ImageSource'; +import type {ImageSource, ImageURISource} from './ImageSource'; import type {ElementRef, Node, RefSetter} from 'react'; +export type ImageSourcePropType = ImageSource; + +/** + * @see ImagePropsIOS.onProgress + */ +export type ImageProgressEventDataIOS = { + loaded: number, + total: number, +}; + +export type ImageErrorEventData = { + error: string, +}; + +export type ImageLoadEventData = { + source: { + height: number, + width: number, + uri: string, + }, +}; + export type ImageLoadEvent = NativeSyntheticEvent< - $ReadOnly<{ - source: $ReadOnly<{ - width: number, - height: number, - uri: string, - }>, - }>, + $ReadOnly, >; -type IOSImageProps = $ReadOnly<{ +export type ImagePropsIOS = $ReadOnly<{ /** * A static image to display while loading the image source. * @@ -55,19 +71,42 @@ type IOSImageProps = $ReadOnly<{ * See https://reactnative.dev/docs/image#onprogress */ onProgress?: ?( - event: NativeSyntheticEvent<$ReadOnly<{loaded: number, total: number}>>, + event: NativeSyntheticEvent<$ReadOnly>, ) => void, }>; -type AndroidImageProps = $ReadOnly<{ - loadingIndicatorSource?: ?(number | $ReadOnly<{uri: string}>), +export type ImagePropsAndroid = $ReadOnly<{ + /** + * similarly to `source`, this property represents the resource used to render + * the loading indicator for the image, displayed until image is ready to be + * displayed, typically after when it got downloaded from network. + */ + loadingIndicatorSource?: ?(number | $ReadOnly), progressiveRenderingEnabled?: ?boolean, fadeDuration?: ?number, /** - * The mechanism that should be used to resize the image when the image's - * dimensions differ from the image view's dimensions. Defaults to `'auto'`. - * See https://reactnative.dev/docs/image#resizemethod-android + * The mechanism that should be used to resize the image when the image's dimensions + * differ from the image view's dimensions. Defaults to `auto`. + * + * - `auto`: Use heuristics to pick between `resize` and `scale`. + * + * - `resize`: A software operation which changes the encoded image in memory before it + * gets decoded. This should be used instead of `scale` when the image is much larger + * than the view. + * + * - `scale`: The image gets drawn downscaled or upscaled. Compared to `resize`, `scale` is + * faster (usually hardware accelerated) and produces higher quality images. This + * should be used if the image is smaller than the view. It should also be used if the + * image is slightly bigger than the view. + * + * - `none`: No sampling is performed and the image is displayed in its full resolution. This + * should only be used in rare circumstances because it is considered unsafe as Android will + * throw a runtime exception when trying to render images that consume too much memory. + * + * More details about `resize` and `scale` can be found at http://frescolib.org/docs/resizing-rotating.html. + * + * @platform android */ resizeMethod?: ?('auto' | 'resize' | 'scale' | 'none'), @@ -81,11 +120,8 @@ type AndroidImageProps = $ReadOnly<{ resizeMultiplier?: ?number, }>; -export type ImageProps = $ReadOnly<{ +export type ImagePropsBase = $ReadOnly<{ ...$Diff>, - ...IOSImageProps, - ...AndroidImageProps, - /** * When true, indicates the image is an accessibility element. * @@ -166,16 +202,15 @@ export type ImageProps = $ReadOnly<{ * See https://reactnative.dev/docs/image#onerror */ onError?: ?( - event: NativeSyntheticEvent< - $ReadOnly<{ - error: string, - }>, - >, + event: NativeSyntheticEvent<$ReadOnly>, ) => void, /** + * onLayout function + * * Invoked on mount and layout changes with - * `{nativeEvent: {layout: {x, y, width, height}}}`. + * + * {nativeEvent: { layout: {x, y, width, height} }}. * * See https://reactnative.dev/docs/image#onlayout */ @@ -206,15 +241,16 @@ export type ImageProps = $ReadOnly<{ /** * The image source (either a remote URL or a local file resource). * + * This prop can also contain several remote URLs, specified together with their width and height and potentially with scale/other URI arguments. + * The native side will then choose the best uri to display based on the measured size of the image container. + * A cache property can be added to control how networked request interacts with the local cache. + * + * The currently supported formats are png, jpg, jpeg, bmp, gif, webp (Android only), psd (iOS only). + * * See https://reactnative.dev/docs/image#source */ source?: ?ImageSource, - /** - * See https://reactnative.dev/docs/image#style - */ - style?: ?ImageStyleProp, - /** * A string indicating which referrer to use when fetching the resource. * Similar to referrerpolicy from HTML. @@ -236,6 +272,26 @@ export type ImageProps = $ReadOnly<{ * Determines how to resize the image when the frame doesn't match the raw * image dimensions. * + * 'cover': Scale the image uniformly (maintain the image's aspect ratio) + * so that both dimensions (width and height) of the image will be equal + * to or larger than the corresponding dimension of the view (minus padding). + * + * 'contain': Scale the image uniformly (maintain the image's aspect ratio) + * so that both dimensions (width and height) of the image will be equal to + * or less than the corresponding dimension of the view (minus padding). + * + * 'stretch': Scale width and height independently, This may change the + * aspect ratio of the src. + * + * 'repeat': Repeat the image to cover the frame of the view. + * The image will keep it's size and aspect ratio. (iOS only) + * + * 'center': Scale the image down so that it is completely visible, + * if bigger than the area of the view. + * The image will not be scaled up. + * + * 'none': Do not resize the image. The image will be displayed at its intrinsic size. + * * See https://reactnative.dev/docs/image#resizemode */ resizeMode?: ?ImageResizeMode, @@ -272,6 +328,17 @@ export type ImageProps = $ReadOnly<{ children?: empty, }>; +export type ImageProps = $ReadOnly<{ + ...ImagePropsIOS, + ...ImagePropsAndroid, + ...ImagePropsBase, + + /** + * See https://reactnative.dev/docs/image#style + */ + style?: ?ImageStyleProp, +}>; + export type ImageBackgroundProps = $ReadOnly<{ ...ImageProps, children?: Node, diff --git a/packages/react-native/Libraries/Image/ImageTypes.flow.js b/packages/react-native/Libraries/Image/ImageTypes.flow.js index eff14cf1d01..8a80a05f442 100644 --- a/packages/react-native/Libraries/Image/ImageTypes.flow.js +++ b/packages/react-native/Libraries/Image/ImageTypes.flow.js @@ -17,8 +17,15 @@ import typeof TextInlineImageNativeComponent from './TextInlineImageNativeCompon import * as React from 'react'; +export type ImageSize = { + width: number, + height: number, +}; + +export type ImageResolvedAssetSource = ResolvedAssetSource; + type ImageComponentStaticsIOS = $ReadOnly<{ - getSize(uri: string): Promise<{width: number, height: number}>, + getSize(uri: string): Promise, getSize( uri: string, success: (width: number, height: number) => void, @@ -28,7 +35,7 @@ type ImageComponentStaticsIOS = $ReadOnly<{ getSizeWithHeaders( uri: string, headers: {[string]: string, ...}, - ): Promise<{width: number, height: number}>, + ): Promise, getSizeWithHeaders( uri: string, headers: {[string]: string, ...}, @@ -46,9 +53,12 @@ type ImageComponentStaticsIOS = $ReadOnly<{ queryCache( urls: Array, - ): Promise<{[string]: 'memory' | 'disk' | 'disk/memory', ...}>, + ): Promise<{[url: string]: 'memory' | 'disk' | 'disk/memory', ...}>, - resolveAssetSource(source: ImageSource): ?ResolvedAssetSource, + /** + * @see https://reactnative.dev/docs/image#resolveassetsource + */ + resolveAssetSource(source: ImageSource): ?ImageResolvedAssetSource, }>; type ImageComponentStaticsAndroid = $ReadOnly<{ @@ -57,7 +67,7 @@ type ImageComponentStaticsAndroid = $ReadOnly<{ }>; export type AbstractImageAndroid = component( - ref: React.RefSetter< + ref?: React.RefSetter< | React.ElementRef | React.ElementRef, >, @@ -67,7 +77,7 @@ export type AbstractImageAndroid = component( export type ImageAndroid = AbstractImageAndroid & ImageComponentStaticsAndroid; export type AbstractImageIOS = component( - ref: React.RefSetter>, + ref?: React.RefSetter>, ...props: ImagePropsType ); diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index ff4788e6118..7fc5e5b5bc6 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -4470,7 +4470,19 @@ declare export function getUrlCacheBreaker(): string; `; exports[`public API should not change unintentionally Libraries/Image/Image.js.flow 1`] = ` -"declare export default Image; +"export type { + ImageProgressEventDataIOS, + ImagePropsIOS, + ImagePropsAndroid, + ImageSourcePropType, + ImageLoadEventData, + ImageErrorEventData, + ImagePropsBase, + ImageProps, + ImageBackgroundProps, +} from \\"./ImageProps\\"; +export type { ImageResolvedAssetSource, ImageSize } from \\"./ImageTypes.flow\\"; +declare export default Image; " `; @@ -4515,33 +4527,40 @@ declare export function useWrapRefWithImageAttachedCallbacks( `; exports[`public API should not change unintentionally Libraries/Image/ImageProps.js 1`] = ` -"export type ImageLoadEvent = NativeSyntheticEvent< - $ReadOnly<{ - source: $ReadOnly<{ - width: number, - height: number, - uri: string, - }>, - }>, +"export type ImageSourcePropType = ImageSource; +export type ImageProgressEventDataIOS = { + loaded: number, + total: number, +}; +export type ImageErrorEventData = { + error: string, +}; +export type ImageLoadEventData = { + source: { + height: number, + width: number, + uri: string, + }, +}; +export type ImageLoadEvent = NativeSyntheticEvent< + $ReadOnly, >; -type IOSImageProps = $ReadOnly<{ +export type ImagePropsIOS = $ReadOnly<{ defaultSource?: ?ImageSource, onPartialLoad?: ?() => void, onProgress?: ?( - event: NativeSyntheticEvent<$ReadOnly<{ loaded: number, total: number }>> + event: NativeSyntheticEvent<$ReadOnly> ) => void, }>; -type AndroidImageProps = $ReadOnly<{ - loadingIndicatorSource?: ?(number | $ReadOnly<{ uri: string }>), +export type ImagePropsAndroid = $ReadOnly<{ + loadingIndicatorSource?: ?(number | $ReadOnly), progressiveRenderingEnabled?: ?boolean, fadeDuration?: ?number, resizeMethod?: ?(\\"auto\\" | \\"resize\\" | \\"scale\\" | \\"none\\"), resizeMultiplier?: ?number, }>; -export type ImageProps = $ReadOnly<{ +export type ImagePropsBase = $ReadOnly<{ ...$Diff>, - ...IOSImageProps, - ...AndroidImageProps, accessible?: ?boolean, internal_analyticTag?: ?string, accessibilityLabel?: ?Stringish, @@ -4554,18 +4573,13 @@ export type ImageProps = $ReadOnly<{ height?: number, width?: number, onError?: ?( - event: NativeSyntheticEvent< - $ReadOnly<{ - error: string, - }>, - > + event: NativeSyntheticEvent<$ReadOnly> ) => void, onLayout?: ?(event: LayoutChangeEvent) => mixed, onLoad?: ?(event: ImageLoadEvent) => void, onLoadEnd?: ?() => void, onLoadStart?: ?() => void, source?: ?ImageSource, - style?: ?ImageStyleProp, referrerPolicy?: ?( | \\"no-referrer\\" | \\"no-referrer-when-downgrade\\" @@ -4583,6 +4597,12 @@ export type ImageProps = $ReadOnly<{ srcSet?: ?string, children?: empty, }>; +export type ImageProps = $ReadOnly<{ + ...ImagePropsIOS, + ...ImagePropsAndroid, + ...ImagePropsBase, + style?: ?ImageStyleProp, +}>; export type ImageBackgroundProps = $ReadOnly<{ ...ImageProps, children?: Node, @@ -4646,8 +4666,13 @@ exports[`public API should not change unintentionally Libraries/Image/ImageSourc `; exports[`public API should not change unintentionally Libraries/Image/ImageTypes.flow.js 1`] = ` -"type ImageComponentStaticsIOS = $ReadOnly<{ - getSize(uri: string): Promise<{ width: number, height: number }>, +"export type ImageSize = { + width: number, + height: number, +}; +export type ImageResolvedAssetSource = ResolvedAssetSource; +type ImageComponentStaticsIOS = $ReadOnly<{ + getSize(uri: string): Promise, getSize( uri: string, success: (width: number, height: number) => void, @@ -4656,7 +4681,7 @@ exports[`public API should not change unintentionally Libraries/Image/ImageTypes getSizeWithHeaders( uri: string, headers: { [string]: string, ... } - ): Promise<{ width: number, height: number }>, + ): Promise, getSizeWithHeaders( uri: string, headers: { [string]: string, ... }, @@ -4671,15 +4696,15 @@ exports[`public API should not change unintentionally Libraries/Image/ImageTypes ): Promise, queryCache( urls: Array - ): Promise<{ [string]: \\"memory\\" | \\"disk\\" | \\"disk/memory\\", ... }>, - resolveAssetSource(source: ImageSource): ?ResolvedAssetSource, + ): Promise<{ [url: string]: \\"memory\\" | \\"disk\\" | \\"disk/memory\\", ... }>, + resolveAssetSource(source: ImageSource): ?ImageResolvedAssetSource, }>; type ImageComponentStaticsAndroid = $ReadOnly<{ ...ImageComponentStaticsIOS, abortPrefetch(requestId: number): void, }>; export type AbstractImageAndroid = component( - ref: React.RefSetter< + ref?: React.RefSetter< | React.ElementRef | React.ElementRef, >, @@ -4687,7 +4712,7 @@ export type AbstractImageAndroid = component( ); export type ImageAndroid = AbstractImageAndroid & ImageComponentStaticsAndroid; export type AbstractImageIOS = component( - ref: React.RefSetter>, + ref?: React.RefSetter>, ...props: ImagePropsType ); export type ImageIOS = AbstractImageIOS & ImageComponentStaticsIOS; diff --git a/packages/react-native/types/__typetests__/index.tsx b/packages/react-native/types/__typetests__/index.tsx index a089ae8e1b1..ed2260f83db 100644 --- a/packages/react-native/types/__typetests__/index.tsx +++ b/packages/react-native/types/__typetests__/index.tsx @@ -1344,8 +1344,8 @@ export class ImageTest extends React.Component { const uri = 'https://seeklogo.com/images/T/typescript-logo-B29A3F462D-seeklogo.com.png'; const headers = {Authorization: 'Bearer test'}; - const image: ImageResolvedAssetSource = Image.resolveAssetSource({uri}); - console.log(image.width, image.height, image.scale, image.uri); + const image = Image.resolveAssetSource({uri}); + console.log(image?.width, image?.height, image?.scale, image?.uri); Image.queryCache && Image.queryCache([uri]).then(({[uri]: status}) => { @@ -1380,14 +1380,14 @@ export class ImageTest extends React.Component { Image.prefetch(uri); // $ExpectType Promise } - handleOnLoad = (e: NativeSyntheticEvent) => { + handleOnLoad = (e: NativeSyntheticEvent>) => { testNativeSyntheticEvent(e); console.log('height:', e.nativeEvent.source.height); console.log('width:', e.nativeEvent.source.width); console.log('uri:', e.nativeEvent.source.uri); }; - handleOnError = (e: NativeSyntheticEvent) => { + handleOnError = (e: NativeSyntheticEvent>) => { testNativeSyntheticEvent(e); console.log('error:', e.nativeEvent.error); }; diff --git a/scripts/build/build-types/buildTypes.js b/scripts/build/build-types/buildTypes.js index 411416478f7..43ec47c53f7 100644 --- a/scripts/build/build-types/buildTypes.js +++ b/scripts/build/build-types/buildTypes.js @@ -80,6 +80,8 @@ const ENTRY_POINTS = [ 'packages/react-native/Libraries/Components/Switch/Switch.js', 'packages/react-native/Libraries/Components/StatusBar/StatusBar.js', 'packages/react-native/Libraries/Components/RefreshControl/RefreshControl.js', + 'packages/react-native/Libraries/Image/Image.js.flow', + 'packages/react-native/Libraries/Image/ImageBackground.js', ]; /**