Skip to content

Commit 3ccf6a0

Browse files
ramya-2196facebook-github-bot
authored andcommitted
Make legacy TypeScript ImageStyle extend ViewStyle (#58907)
Summary: ## Motivation Projects on the default legacy TypeScript types get type errors when they set valid style props on `<Image>`. This is coming from [issue](#52957) ## What is the Issue About? It reports that `filter`, `boxShadow`, `mixBlendMode`, `borderTopStartRadius` and `outlineColor` props give TS error. These props are already accepted by the opt-in strict API types. ## Background React Native ships two sets of TypeScript style types: - **Legacy (default):** hand-written in `types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts`. Maintained by hand, so it drifts. - **Strict API (opt-in):** generated from the Flow source of truth, `Libraries/StyleSheet/StyleSheetTypes.js`, so it cannot drift. ## Root Cause In Flow, `ImageStyle` spreads `ViewStyle` and only narrows `overflow` to `visible | hidden`. The legacy `ImageStyle` instead extended `FlexStyle`/`ShadowStyleIOS`/`TransformsStyle` and redeclared a hand-picked subset of view props, so it rejected 37 `ViewStyle` props: - Border and outline colours: `borderTopColor`, `borderBottomColor`, `borderLeftColor`, `borderRightColor`, `borderStartColor`, `borderEndColor`, `borderBlockColor`, `borderBlockStartColor`, `borderBlockEndColor`, `outlineColor` - Logical corner radii: `borderTopStartRadius`, `borderTopEndRadius`, `borderBottomStartRadius`, `borderBottomEndRadius`, `borderStartStartRadius`, `borderStartEndRadius`, `borderEndStartRadius`, `borderEndEndRadius` - `borderCurve`, `borderStyle`, `outlineStyle`, `outlineWidth`, `outlineOffset`, `elevation`, `pointerEvents`, `isolation`, `boxShadow`, `filter`, `mixBlendMode` - Background layers: `backgroundImage`, `backgroundSize`, `backgroundPosition`, `backgroundRepeat` and their `experimental_` versions `TextStyle` already extends `ViewStyle`, so it is not affected. ## Description of Changes - `ImageStyle` now extends `ViewStyle`, narrows `overflow` to `visible | hidden`, and keeps only the Image-specific props (`resizeMode`, `overlayColor`, `tintColor`, `objectFit`). The 10 view props it used to redeclare are now inherited with identical types. New `ViewStyle` props flow through automatically, as they already do for `TextStyle` and for the generated types. - The type test `__typetests__/stylesheet-style-props.tsx` (renamed from the earlier issue reproducer) sets all 37 props on an Image style. It asserts that `ImageStyle` and `TextStyle` declare every `ViewStyle` key, so future drift fails CI with the missing prop names. Negative checks confirm `ImageStyle` still rejects `overflow: "scroll"` and text-only props. The change only widens what `ImageStyle` accepts, matching the generated types. Changelog: [General][Fixed] - Fix the legacy TypeScript `ImageStyle` type rejecting valid `ViewStyle` props such as `filter`, `boxShadow` and `mixBlendMode` Differential Revision: D123003237
1 parent df5aa64 commit 3ccf6a0

2 files changed

Lines changed: 105 additions & 11 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
// The hand-written legacy ImageStyle and TextStyle must accept every ViewStyle
11+
// prop, as the generated (strict API) types do. ImageStyle only narrows
12+
// `overflow`. See https://github.com/facebook/react-native/issues/52957
13+
14+
import * as React from 'react';
15+
// @ts-ignore
16+
import {
17+
Image,
18+
View,
19+
type ImageStyle,
20+
type TextStyle,
21+
type ViewStyle,
22+
} from 'react-native';
23+
24+
type MissingKeys<Target, Source> = Exclude<keyof Source, keyof Target>;
25+
26+
// On failure, the error names every ViewStyle prop the target type lacks.
27+
const imageStyleHasAllViewStyleKeys: [
28+
MissingKeys<ImageStyle, Omit<ViewStyle, 'overflow'>>,
29+
] extends [never]
30+
? true
31+
: MissingKeys<ImageStyle, Omit<ViewStyle, 'overflow'>> = true;
32+
33+
const textStyleHasAllViewStyleKeys: [
34+
MissingKeys<TextStyle, ViewStyle>,
35+
] extends [never]
36+
? true
37+
: MissingKeys<TextStyle, ViewStyle> = true;
38+
39+
function viewStyleToImageStyle(style: Omit<ViewStyle, 'overflow'>): ImageStyle {
40+
return style;
41+
}
42+
43+
const imageStyle: ImageStyle = {
44+
borderBlockColor: 'red',
45+
borderBlockEndColor: 'red',
46+
borderBlockStartColor: 'red',
47+
borderBottomColor: 'red',
48+
borderEndColor: 'red',
49+
borderLeftColor: 'red',
50+
borderRightColor: 'red',
51+
borderStartColor: 'red',
52+
borderTopColor: 'red',
53+
borderBottomEndRadius: 4,
54+
borderBottomStartRadius: 4,
55+
borderEndEndRadius: 4,
56+
borderEndStartRadius: 4,
57+
borderStartEndRadius: 4,
58+
borderStartStartRadius: 4,
59+
borderTopEndRadius: 4,
60+
borderTopStartRadius: 4,
61+
borderCurve: 'continuous',
62+
borderStyle: 'dashed',
63+
outlineColor: 'blue',
64+
outlineOffset: 2,
65+
outlineStyle: 'solid',
66+
outlineWidth: 1,
67+
elevation: 2,
68+
pointerEvents: 'none',
69+
isolation: 'isolate',
70+
boxShadow: '0 2px 4px black',
71+
filter: 'blur(4px)',
72+
mixBlendMode: 'multiply',
73+
backgroundImage: 'linear-gradient(red, blue)',
74+
experimental_backgroundImage: 'linear-gradient(red, blue)',
75+
backgroundSize: '50% 50%',
76+
experimental_backgroundSize: '50% 50%',
77+
backgroundPosition: 'center',
78+
experimental_backgroundPosition: 'center',
79+
backgroundRepeat: 'no-repeat',
80+
experimental_backgroundRepeat: 'no-repeat',
81+
};
82+
83+
const imageStyleWithScrollOverflow: ImageStyle = {
84+
// @ts-expect-error Image does not support `overflow: 'scroll'`.
85+
overflow: 'scroll',
86+
};
87+
88+
const imageStyleWithTextProp: ImageStyle = {
89+
// @ts-expect-error `fontSize` is not an Image style prop.
90+
fontSize: 12,
91+
};
92+
93+
const viewStyle: ViewStyle = {
94+
backgroundColor: 'white',
95+
};
96+
97+
export function App() {
98+
return (
99+
<View style={viewStyle}>
100+
<Image style={imageStyle} />
101+
<Image style={viewStyleToImageStyle(viewStyle)} />
102+
</View>
103+
);
104+
}

‎packages/react-native/types_DEPRECATED/Libraries/StyleSheet/StyleSheetTypes.d.ts‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -649,20 +649,10 @@ export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
649649
* Image style
650650
* @see https://reactnative.dev/docs/image#style
651651
*/
652-
export interface ImageStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
652+
export interface ImageStyle extends ViewStyle {
653653
resizeMode?: ImageResizeMode | undefined;
654-
backfaceVisibility?: 'visible' | 'hidden' | undefined;
655-
borderBottomLeftRadius?: AnimatableNumericValue | string | undefined;
656-
borderBottomRightRadius?: AnimatableNumericValue | string | undefined;
657-
backgroundColor?: ColorValue | undefined;
658-
borderColor?: ColorValue | undefined;
659-
borderRadius?: AnimatableNumericValue | string | undefined;
660-
borderTopLeftRadius?: AnimatableNumericValue | string | undefined;
661-
borderTopRightRadius?: AnimatableNumericValue | string | undefined;
662654
overflow?: 'visible' | 'hidden' | undefined;
663655
overlayColor?: ColorValue | undefined;
664656
tintColor?: ColorValue | undefined;
665-
opacity?: AnimatableNumericValue | undefined;
666657
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none' | undefined;
667-
cursor?: CursorValue | undefined;
668658
}

0 commit comments

Comments
 (0)