mirror of
https://github.com/zoriya/react-native-web.git
synced 2025-12-06 06:36:13 +00:00
[change] Move all non-standard CSS transforms to 'preprocess' step
This patch reorganizes the style compiler so that the 'preprocess' step is responsible for all the work needed to transform any non-standard CSS from React Native into a form that can be 'compiled' to rules for the CSSStyleSheet. Over time the 'preprocess' step should eventually be unnecessary as React Native aligns its APIs with CSS APIs. And any external style compilers should be able to run the 'preprocess' function over the style input to produce valid CSS as input for the compiler.
This commit is contained in:
@@ -72,12 +72,6 @@ describe('compiler/createReactDOMStyle', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test('aspectRatio', () => {
|
||||
expect(createReactDOMStyle({ aspectRatio: 9 / 16 })).toEqual({
|
||||
aspectRatio: '0.5625'
|
||||
});
|
||||
});
|
||||
|
||||
describe('flexbox styles', () => {
|
||||
test('flex: -1', () => {
|
||||
expect(createReactDOMStyle({ flex: -1 })).toEqual({
|
||||
@@ -190,70 +184,4 @@ describe('compiler/createReactDOMStyle', () => {
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
test('fontVariant', () => {
|
||||
expect(
|
||||
createReactDOMStyle({ fontVariant: 'common-ligatures small-caps' })
|
||||
).toEqual({
|
||||
fontVariant: 'common-ligatures small-caps'
|
||||
});
|
||||
|
||||
expect(
|
||||
createReactDOMStyle({ fontVariant: ['common-ligatures', 'small-caps'] })
|
||||
).toEqual({
|
||||
fontVariant: 'common-ligatures small-caps'
|
||||
});
|
||||
});
|
||||
|
||||
test('textAlignVertical', () => {
|
||||
expect(
|
||||
createReactDOMStyle({
|
||||
textAlignVertical: 'center'
|
||||
})
|
||||
).toEqual({
|
||||
verticalAlign: 'middle'
|
||||
});
|
||||
});
|
||||
|
||||
test('verticalAlign', () => {
|
||||
expect(
|
||||
createReactDOMStyle({
|
||||
verticalAlign: 'top',
|
||||
textAlignVertical: 'center'
|
||||
})
|
||||
).toEqual({
|
||||
verticalAlign: 'top'
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
// passthrough if transform value is ever a string
|
||||
test('string', () => {
|
||||
const transform =
|
||||
'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)';
|
||||
const style = { transform };
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({ transform });
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
{ perspective: 50 },
|
||||
{ scaleX: 20 },
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' },
|
||||
{ matrix: [1, 2, 3, 4, 5, 6] },
|
||||
{ matrix3d: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] }
|
||||
]
|
||||
};
|
||||
const resolved = createReactDOMStyle(style);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
transform:
|
||||
'perspective(50px) scaleX(20) translateX(20px) rotate(20deg) matrix(1,2,3,4,5,6) matrix3d(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4)'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('StyleSheet/compile', () => {
|
||||
describe('atomic', () => {
|
||||
test('converts style to atomic CSS', () => {
|
||||
const result = atomic({
|
||||
animationDirection: ['alternate', 'alternate-reverse'],
|
||||
animationDirection: 'alternate,alternate-reverse',
|
||||
animationKeyframes: [
|
||||
{ '0%': { top: 0 }, '50%': { top: 5 }, '100%': { top: 10 } },
|
||||
{ from: { left: 0 }, to: { left: 10 } }
|
||||
@@ -34,7 +34,7 @@ describe('StyleSheet/compile', () => {
|
||||
{
|
||||
"$$css": true,
|
||||
"$$css$localize": true,
|
||||
"animationDirection": "r-animationDirection-1kmv48j",
|
||||
"animationDirection": "r-animationDirection-1wgwto7",
|
||||
"animationKeyframes": "r-animationKeyframes-zacbmr",
|
||||
"fontFamily": "r-fontFamily-1qd0xha",
|
||||
"insetInlineStart": [
|
||||
@@ -63,7 +63,7 @@ describe('StyleSheet/compile', () => {
|
||||
[
|
||||
[
|
||||
[
|
||||
".r-animationDirection-1kmv48j{animation-direction:alternate,alternate-reverse;}",
|
||||
".r-animationDirection-1wgwto7{animation-direction:alternate,alternate-reverse;}",
|
||||
],
|
||||
3,
|
||||
],
|
||||
|
||||
@@ -75,6 +75,76 @@ describe('StyleSheet/preprocess', () => {
|
||||
paddingInlineStart: 2
|
||||
});
|
||||
});
|
||||
|
||||
test('converts non-standard textAlignVertical', () => {
|
||||
expect(
|
||||
preprocess({
|
||||
textAlignVertical: 'center'
|
||||
})
|
||||
).toEqual({
|
||||
verticalAlign: 'middle'
|
||||
});
|
||||
|
||||
expect(
|
||||
preprocess({
|
||||
verticalAlign: 'top',
|
||||
textAlignVertical: 'center'
|
||||
})
|
||||
).toEqual({
|
||||
verticalAlign: 'top'
|
||||
});
|
||||
});
|
||||
|
||||
test('aspectRatio', () => {
|
||||
expect(preprocess({ aspectRatio: 9 / 16 })).toEqual({
|
||||
aspectRatio: '0.5625'
|
||||
});
|
||||
});
|
||||
|
||||
test('fontVariant', () => {
|
||||
expect(
|
||||
preprocess({ fontVariant: 'common-ligatures small-caps' })
|
||||
).toEqual({
|
||||
fontVariant: 'common-ligatures small-caps'
|
||||
});
|
||||
|
||||
expect(
|
||||
preprocess({ fontVariant: ['common-ligatures', 'small-caps'] })
|
||||
).toEqual({
|
||||
fontVariant: 'common-ligatures small-caps'
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
// passthrough if transform value is ever a string
|
||||
test('string', () => {
|
||||
const transform =
|
||||
'perspective(50px) scaleX(20) translateX(20px) rotate(20deg)';
|
||||
const style = { transform };
|
||||
const resolved = preprocess(style);
|
||||
|
||||
expect(resolved).toEqual({ transform });
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const style = {
|
||||
transform: [
|
||||
{ perspective: 50 },
|
||||
{ scaleX: 20 },
|
||||
{ translateX: 20 },
|
||||
{ rotate: '20deg' },
|
||||
{ matrix: [1, 2, 3, 4, 5, 6] },
|
||||
{ matrix3d: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] }
|
||||
]
|
||||
};
|
||||
const resolved = preprocess(style);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
transform:
|
||||
'perspective(50px) scaleX(20) translateX(20px) rotate(20deg) matrix(1,2,3,4,5,6) matrix3d(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4)'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('preprocesses multiple shadow styles into a single declaration', () => {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
import normalizeValueWithProperty from './normalizeValueWithProperty';
|
||||
import canUseDOM from '../../../modules/canUseDom';
|
||||
import { warnOnce } from '../../../modules/warnOnce';
|
||||
|
||||
type Style = { [key: string]: any };
|
||||
|
||||
@@ -33,13 +32,6 @@ const supportsCSS3TextDecoration =
|
||||
(window.CSS.supports('text-decoration-line', 'none') ||
|
||||
window.CSS.supports('-webkit-text-decoration-line', 'none')));
|
||||
|
||||
const ignoredProps = {
|
||||
elevation: true,
|
||||
overlayColor: true,
|
||||
resizeMode: true,
|
||||
tintColor: true
|
||||
};
|
||||
|
||||
const MONOSPACE_FONT_STACK = 'monospace,monospace';
|
||||
|
||||
const SYSTEM_FONT_STACK =
|
||||
@@ -114,36 +106,6 @@ const STYLE_SHORT_FORM_EXPANSIONS = {
|
||||
//paddingInlineEnd: ['marginRight'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform
|
||||
*/
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
// { matrix: [1,2,3,4,5,6] } => 'matrix(1,2,3,4,5,6)'
|
||||
const mapTransform = (transform: Object): string => {
|
||||
const type = Object.keys(transform)[0];
|
||||
const value = transform[type];
|
||||
if (type === 'matrix' || type === 'matrix3d') {
|
||||
return `${type}(${value.join(',')})`;
|
||||
} else {
|
||||
const normalizedValue = normalizeValueWithProperty(value, type);
|
||||
return `${type}(${normalizedValue})`;
|
||||
}
|
||||
};
|
||||
|
||||
export const createTransformValue = (style: Style): string => {
|
||||
let transform = style.transform;
|
||||
if (Array.isArray(style.transform)) {
|
||||
warnOnce(
|
||||
'transform',
|
||||
'"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".'
|
||||
);
|
||||
transform = style.transform.map(mapTransform).join(' ');
|
||||
}
|
||||
return transform;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reducer
|
||||
*/
|
||||
@@ -160,16 +122,12 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => {
|
||||
|
||||
if (
|
||||
// Ignore everything with a null value
|
||||
value == null ||
|
||||
// Ignore some React Native styles
|
||||
ignoredProps[prop]
|
||||
value == null
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop === 'aspectRatio') {
|
||||
resolvedStyle[prop] = value.toString();
|
||||
} else if (prop === 'backgroundClip') {
|
||||
if (prop === 'backgroundClip') {
|
||||
// TODO: remove once this issue is fixed
|
||||
// https://github.com/rofrischmann/inline-style-prefixer/issues/159
|
||||
if (value === 'text') {
|
||||
@@ -196,24 +154,6 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => {
|
||||
} else {
|
||||
resolvedStyle[prop] = value;
|
||||
}
|
||||
} else if (prop === 'fontVariant') {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
warnOnce(
|
||||
'fontVariant',
|
||||
'"fontVariant" style array value is deprecated. Use space-separated values.'
|
||||
);
|
||||
resolvedStyle.fontVariant = value.join(' ');
|
||||
} else {
|
||||
resolvedStyle[prop] = value;
|
||||
}
|
||||
} else if (prop === 'textAlignVertical') {
|
||||
warnOnce(
|
||||
'textAlignVertical',
|
||||
'"textAlignVertical" style is deprecated. Use "verticalAlign".'
|
||||
);
|
||||
if (resolvedStyle.verticalAlign == null) {
|
||||
resolvedStyle.verticalAlign = value === 'center' ? 'middle' : value;
|
||||
}
|
||||
} else if (prop === 'textDecorationLine') {
|
||||
// use 'text-decoration' for browsers that only support CSS2
|
||||
// text-decoration (e.g., IE, Edge)
|
||||
@@ -222,8 +162,6 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => {
|
||||
} else {
|
||||
resolvedStyle.textDecorationLine = value;
|
||||
}
|
||||
} else if (prop === 'transform' || prop === 'transformMatrix') {
|
||||
resolvedStyle.transform = createTransformValue(style);
|
||||
} else if (prop === 'writingDirection') {
|
||||
resolvedStyle.direction = value;
|
||||
} else {
|
||||
@@ -265,7 +203,7 @@ const createReactDOMStyle = (style: Style, isInline?: boolean): Style => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolvedStyle[prop] = Array.isArray(value) ? value.join(',') : value;
|
||||
resolvedStyle[prop] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,23 @@ export const createTextShadowValue = (style: Object): void | string => {
|
||||
}
|
||||
};
|
||||
|
||||
// { scale: 2 } => 'scale(2)'
|
||||
// { translateX: 20 } => 'translateX(20px)'
|
||||
// { matrix: [1,2,3,4,5,6] } => 'matrix(1,2,3,4,5,6)'
|
||||
const mapTransform = (transform: Object): string => {
|
||||
const type = Object.keys(transform)[0];
|
||||
const value = transform[type];
|
||||
if (type === 'matrix' || type === 'matrix3d') {
|
||||
return `${type}(${value.join(',')})`;
|
||||
} else {
|
||||
const normalizedValue = normalizeValueWithProperty(value, type);
|
||||
return `${type}(${normalizedValue})`;
|
||||
}
|
||||
};
|
||||
export const createTransformValue = (value: Array<Object>): string => {
|
||||
return value.map(mapTransform).join(' ');
|
||||
};
|
||||
|
||||
const PROPERTIES_STANDARD: { [key: string]: string } = {
|
||||
borderBottomEndRadius: 'borderEndEndRadius',
|
||||
borderBottomStartRadius: 'borderEndStartRadius',
|
||||
@@ -79,6 +96,13 @@ const PROPERTIES_STANDARD: { [key: string]: string } = {
|
||||
start: 'insetInlineStart'
|
||||
};
|
||||
|
||||
const ignoredProps = {
|
||||
elevation: true,
|
||||
overlayColor: true,
|
||||
resizeMode: true,
|
||||
tintColor: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Preprocess styles
|
||||
*/
|
||||
@@ -88,9 +112,64 @@ export const preprocess = <T: {| [key: string]: any |}>(
|
||||
const style = originalStyle || emptyObject;
|
||||
const nextStyle = {};
|
||||
|
||||
// Convert shadow styles
|
||||
if (
|
||||
style.shadowColor != null ||
|
||||
style.shadowOffset != null ||
|
||||
style.shadowOpacity != null ||
|
||||
style.shadowRadius != null
|
||||
) {
|
||||
warnOnce(
|
||||
'shadowStyles',
|
||||
`"shadow*" style props are deprecated. Use "boxShadow".`
|
||||
);
|
||||
const boxShadowValue = createBoxShadowValue(style);
|
||||
if (boxShadowValue != null && nextStyle.boxShadow == null) {
|
||||
const { boxShadow } = style;
|
||||
const value = boxShadow
|
||||
? `${boxShadow}, ${boxShadowValue}`
|
||||
: boxShadowValue;
|
||||
nextStyle.boxShadow = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert text shadow styles
|
||||
if (
|
||||
style.textShadowColor != null ||
|
||||
style.textShadowOffset != null ||
|
||||
style.textShadowRadius != null
|
||||
) {
|
||||
warnOnce(
|
||||
'textShadowStyles',
|
||||
`"textShadow*" style props are deprecated. Use "textShadow".`
|
||||
);
|
||||
const textShadowValue = createTextShadowValue(style);
|
||||
if (textShadowValue != null && nextStyle.textShadow == null) {
|
||||
const { textShadow } = style;
|
||||
const value = textShadow
|
||||
? `${textShadow}, ${textShadowValue}`
|
||||
: textShadowValue;
|
||||
nextStyle.textShadow = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const originalProp in style) {
|
||||
if (
|
||||
// Ignore some React Native styles
|
||||
ignoredProps[originalProp] != null ||
|
||||
originalProp === 'shadowColor' ||
|
||||
originalProp === 'shadowOffset' ||
|
||||
originalProp === 'shadowOpacity' ||
|
||||
originalProp === 'shadowRadius' ||
|
||||
originalProp === 'textShadowColor' ||
|
||||
originalProp === 'textShadowOffset' ||
|
||||
originalProp === 'textShadowRadius'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalValue = style[originalProp];
|
||||
let prop = PROPERTIES_STANDARD[originalProp] || originalProp;
|
||||
const prop = PROPERTIES_STANDARD[originalProp] || originalProp;
|
||||
let value = originalValue;
|
||||
|
||||
if (
|
||||
@@ -100,42 +179,37 @@ export const preprocess = <T: {| [key: string]: any |}>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert shadow styles
|
||||
if (
|
||||
prop === 'shadowColor' ||
|
||||
prop === 'shadowOffset' ||
|
||||
prop === 'shadowOpacity' ||
|
||||
prop === 'shadowRadius'
|
||||
) {
|
||||
const boxShadowValue = createBoxShadowValue(style);
|
||||
if (boxShadowValue != null && nextStyle.boxShadow == null) {
|
||||
const { boxShadow } = style;
|
||||
prop = 'boxShadow';
|
||||
value = boxShadow ? `${boxShadow}, ${boxShadowValue}` : boxShadowValue;
|
||||
} else {
|
||||
continue;
|
||||
if (prop === 'aspectRatio') {
|
||||
nextStyle[prop] = value.toString();
|
||||
} else if (prop === 'fontVariant') {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
warnOnce(
|
||||
'fontVariant',
|
||||
'"fontVariant" style array value is deprecated. Use space-separated values.'
|
||||
);
|
||||
value = value.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Convert text shadow styles
|
||||
if (
|
||||
prop === 'textShadowColor' ||
|
||||
prop === 'textShadowOffset' ||
|
||||
prop === 'textShadowRadius'
|
||||
) {
|
||||
const textShadowValue = createTextShadowValue(style);
|
||||
if (textShadowValue != null && nextStyle.textShadow == null) {
|
||||
const { textShadow } = style;
|
||||
prop = 'textShadow';
|
||||
value = textShadow
|
||||
? `${textShadow}, ${textShadowValue}`
|
||||
: textShadowValue;
|
||||
} else {
|
||||
continue;
|
||||
nextStyle[prop] = value;
|
||||
} else if (prop === 'textAlignVertical') {
|
||||
warnOnce(
|
||||
'textAlignVertical',
|
||||
'"textAlignVertical" style is deprecated. Use "verticalAlign".'
|
||||
);
|
||||
if (style.verticalAlign == null) {
|
||||
nextStyle.verticalAlign = value === 'center' ? 'middle' : value;
|
||||
}
|
||||
} else if (prop === 'transform') {
|
||||
if (Array.isArray(value)) {
|
||||
warnOnce(
|
||||
'transform',
|
||||
'"transform" style array value is deprecated. Use space-separated string functions, e.g., "scaleX(2) rotateX(15deg)".'
|
||||
);
|
||||
value = createTransformValue(value);
|
||||
}
|
||||
nextStyle.transform = value;
|
||||
} else {
|
||||
nextStyle[prop] = value;
|
||||
}
|
||||
|
||||
nextStyle[prop] = value;
|
||||
}
|
||||
|
||||
// $FlowIgnore
|
||||
|
||||
@@ -68,15 +68,10 @@ export function validate(obj: Object) {
|
||||
let suggestion = '';
|
||||
if (prop === 'animation' || prop === 'animationName') {
|
||||
suggestion = 'Did you mean "animationKeyframes"?';
|
||||
// } else if (prop === 'boxShadow') {
|
||||
// suggestion = 'Did you mean "shadow{Color,Offset,Opacity,Radius}"?';
|
||||
isInvalid = true;
|
||||
} else if (prop === 'direction') {
|
||||
suggestion = 'Did you mean "writingDirection"?';
|
||||
isInvalid = true;
|
||||
} else if (prop === 'verticalAlign') {
|
||||
suggestion = 'Did you mean "textAlignVertical"?';
|
||||
isInvalid = true;
|
||||
} else if (invalidShortforms[prop]) {
|
||||
suggestion = 'Please use long-form properties.';
|
||||
isInvalid = true;
|
||||
|
||||
Reference in New Issue
Block a user