mirror of
https://github.com/zoriya/react-native-web.git
synced 2025-12-06 06:36:13 +00:00
[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:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"', () => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -306,7 +306,7 @@ export type LayoutStyles = {|
|
||||
export type ShadowStyles = {|
|
||||
// @deprecated
|
||||
shadowColor?: ?ColorValue,
|
||||
shadowOffset?: {|
|
||||
shadowOffset?: ?{|
|
||||
width?: DimensionValue,
|
||||
height?: DimensionValue
|
||||
|},
|
||||
|
||||
Reference in New Issue
Block a user