[fix] Image: style resolving

Cannot pass the result of StyleSheet.flatten to components, as it mixes
dynamic and static (compiled away) style information.

With the way Image is currently implemented, we have to override
'boxShadow' in all cases to avoid the 'shadow*' props being incorrectly
applied as a box-shadow. Once Image is implemented using createElement,
we can disable the 'boxShadow" generation just for Image.

Fix #2527
This commit is contained in:
Nicolas Gallagher
2023-05-31 13:29:52 -07:00
parent 39b94b1945
commit a2bf0a573e
10 changed files with 92 additions and 58 deletions

4
package-lock.json generated
View File

@@ -15486,7 +15486,7 @@
"normalize-css-color": "^1.0.2",
"nullthrows": "^1.1.1",
"postcss-value-parser": "^4.2.0",
"styleq": "^0.1.2"
"styleq": "^0.1.3"
},
"peerDependencies": {
"react": "^18.0.0",
@@ -23633,7 +23633,7 @@
"normalize-css-color": "^1.0.2",
"nullthrows": "^1.1.1",
"postcss-value-parser": "^4.2.0",
"styleq": "^0.1.2"
"styleq": "^0.1.3"
}
},
"react-native-web-docs": {

View File

@@ -29,7 +29,7 @@
"normalize-css-color": "^1.0.2",
"nullthrows": "^1.1.1",
"postcss-value-parser": "^4.2.0",
"styleq": "^0.1.2"
"styleq": "^0.1.3"
},
"peerDependencies": {
"react": "^18.0.0",

View File

@@ -351,7 +351,7 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
</div>
`;
exports[`components/Image prop "style" supports "shadow" properties (convert to filter) 1`] = `
exports[`components/Image prop "style" supports "shadow" properties (converts to filter) 1`] = `
<div
class="css-view-175oi2r r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
@@ -362,6 +362,17 @@ exports[`components/Image prop "style" supports "shadow" properties (convert to
</div>
`;
exports[`components/Image prop "style" supports static and dynamic styles 1`] = `
<div
class="css-view-175oi2r r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010 pos-abs"
style="transition-timing-function: ease-in;"
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
/>
</div>
`;
exports[`components/Image prop "testID" 1`] = `
<div
class="css-view-175oi2r r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
@@ -379,7 +390,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
>
<div
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-57);"
/>
<img
alt=""
@@ -392,7 +403,7 @@ exports[`components/Image prop "tintColor" convert to filter 1`] = `
>
<defs>
<filter
id="tint-56"
id="tint-57"
>
<feflood
flood-color="red"

View File

@@ -345,7 +345,7 @@ describe('components/Image', () => {
});
describe('prop "style"', () => {
test('supports "shadow" properties (convert to filter)', () => {
test('supports "shadow" properties (converts to filter)', () => {
const { container } = render(
<Image
style={{ shadowColor: 'red', shadowOffset: { width: 1, height: 1 } }}
@@ -360,6 +360,18 @@ describe('components/Image', () => {
);
expect(container.firstChild).toMatchSnapshot();
});
test('supports static and dynamic styles', () => {
const { container } = render(
<Image
style={[
{ $$css: true, position: 'pos-abs' },
{ transitionTimingFunction: 'ease-in' }
]}
/>
);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "tintColor"', () => {

View File

@@ -51,7 +51,12 @@ function createTintColorSVG(tintColor, id) {
) : null;
}
function getFlatStyle(style, blurRadius, filterId, tintColorProp) {
function extractNonStandardStyleProps(
style,
blurRadius,
filterId,
tintColorProp
) {
const flatStyle = StyleSheet.flatten(style);
const { filter, resizeMode, shadowOffset, tintColor } = flatStyle;
@@ -93,19 +98,7 @@ function getFlatStyle(style, blurRadius, filterId, tintColorProp) {
_filter = filters.join(' ');
}
// These styles are converted to CSS filters applied to the
// element displaying the background image.
delete flatStyle.blurRadius;
delete flatStyle.shadowColor;
delete flatStyle.shadowOpacity;
delete flatStyle.shadowOffset;
delete flatStyle.shadowRadius;
delete flatStyle.tintColor;
// These styles are not supported on View
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
return [flatStyle, resizeMode, _filter, tintColor];
return [resizeMode, _filter, tintColor];
}
function resolveAssetDimensions(source) {
@@ -223,7 +216,7 @@ const Image: React.AbstractComponent<
const requestRef = React.useRef(null);
const shouldDisplaySource =
state === LOADED || (state === LOADING && defaultSource == null);
const [flatStyle, _resizeMode, filter, _tintColor] = getFlatStyle(
const [_resizeMode, filter, _tintColor] = extractNonStandardStyleProps(
style,
blurRadius,
filterRef.current,
@@ -335,7 +328,11 @@ const Image: React.AbstractComponent<
styles.root,
hasTextAncestor && styles.inline,
imageSizeStyle,
flatStyle
style,
styles.undo,
// TEMP: avoid deprecated shadow props regression
// until Image refactored to use createElement.
{ boxShadow: null }
]}
>
<View
@@ -383,6 +380,19 @@ const styles = StyleSheet.create({
inline: {
display: 'inline-flex'
},
undo: {
// These styles are converted to CSS filters applied to the
// element displaying the background image.
blurRadius: null,
shadowColor: null,
shadowOpacity: null,
shadowOffset: null,
shadowRadius: null,
tintColor: null,
// These styles are not supported
overlayColor: null,
resizeMode: null
},
image: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'transparent',

View File

@@ -9,16 +9,7 @@
*/
import type { ColorValue, GenericStyleProp } from '../../types';
import type { ViewProps } from '../View/types';
import type {
AnimationStyles,
BorderStyles,
InteractionStyles,
LayoutStyles,
ShadowStyles,
TransformStyles
} from '../../types/styles';
import type { ViewProps, ViewStyle } from '../View/types';
type SourceObject = {
/**
@@ -88,16 +79,7 @@ export type ResizeMode =
export type Source = number | string | SourceObject | Array<SourceObject>;
export type ImageStyle = {
...AnimationStyles,
...BorderStyles,
...InteractionStyles,
...LayoutStyles,
...ShadowStyles,
...TransformStyles,
backgroundColor?: ColorValue,
boxShadow?: string,
filter?: string,
opacity?: number,
...ViewStyle,
// @deprecated
resizeMode?: ResizeMode,
tintColor?: ColorValue

View File

@@ -18,14 +18,21 @@ import canUseDOM from '../../modules/canUseDom';
const staticStyleMap: WeakMap<Object, Object> = new WeakMap();
const sheet = createSheet();
function customStyleq(styles, isRTL) {
const defaultPreprocessOptions = { shadow: true, textShadow: true };
function customStyleq(styles, options: Options = {}) {
const { writingDirection, ...preprocessOptions } = options;
const isRTL = writingDirection === 'rtl';
return styleq.factory({
transform(style) {
const compiledStyle = staticStyleMap.get(style);
if (compiledStyle != null) {
return localizeStyle(compiledStyle, isRTL);
}
return preprocess(style);
return preprocess(style, {
...defaultPreprocessOptions,
...preprocessOptions
});
}
})(styles);
}
@@ -41,7 +48,9 @@ function insertRules(compiledOrderedRules) {
}
function compileAndInsertAtomic(style) {
const [compiledStyle, compiledOrderedRules] = atomic(preprocess(style));
const [compiledStyle, compiledOrderedRules] = atomic(
preprocess(style, defaultPreprocessOptions)
);
insertRules(compiledOrderedRules);
return compiledStyle;
}
@@ -141,11 +150,15 @@ function getSheet(): { id: string, textContent: string } {
* resolve
*/
type StyleProps = [string, { [key: string]: mixed } | null];
type Options = { writingDirection: 'ltr' | 'rtl' };
type Options = {
shadow?: boolean,
textShadow?: boolean,
writingDirection: 'ltr' | 'rtl'
};
function StyleSheet(styles: any, options?: Options): StyleProps {
const isRTL = options != null && options.writingDirection === 'rtl';
const styleProps: StyleProps = customStyleq(styles, isRTL);
function StyleSheet(styles: any, options?: Options = {}): StyleProps {
const isRTL = options.writingDirection === 'rtl';
const styleProps: StyleProps = customStyleq(styles, options);
if (Array.isArray(styleProps) && styleProps[1] != null) {
styleProps[1] = inline(styleProps[1], isRTL);
}

View File

@@ -107,17 +107,19 @@ const ignoredProps = {
* Preprocess styles
*/
export const preprocess = <T: {| [key: string]: any |}>(
originalStyle: T
originalStyle: T,
options?: { shadow?: boolean, textShadow?: boolean } = {}
): T => {
const style = originalStyle || emptyObject;
const nextStyle = {};
// Convert shadow styles
if (
(options.shadow === true,
style.shadowColor != null ||
style.shadowOffset != null ||
style.shadowOpacity != null ||
style.shadowRadius != null
style.shadowOffset != null ||
style.shadowOpacity != null ||
style.shadowRadius != null)
) {
warnOnce(
'shadowStyles',
@@ -135,9 +137,10 @@ export const preprocess = <T: {| [key: string]: any |}>(
// Convert text shadow styles
if (
(options.textShadow === true,
style.textShadowColor != null ||
style.textShadowOffset != null ||
style.textShadowRadius != null
style.textShadowOffset != null ||
style.textShadowRadius != null)
) {
warnOnce(
'textShadowStyles',

View File

@@ -787,7 +787,10 @@ const createDOMProps = (elementType, props, options) => {
}
const [className, inlineStyle] = StyleSheet(
[style, pointerEvents && pointerEventsStyles[pointerEvents]],
{ writingDirection: options ? options.writingDirection : 'ltr' }
{
writingDirection: 'ltr',
...options
}
);
if (className) {
domProps.className = className;

View File

@@ -306,7 +306,7 @@ export type LayoutStyles = {|
export type ShadowStyles = {|
// @deprecated
shadowColor?: ?ColorValue,
shadowOffset?: {|
shadowOffset?: ?{|
width?: DimensionValue,
height?: DimensionValue
|},