mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-04 19:05:49 +00:00
[change] StyleSheet validation
Stop relying on React internals and propTypes validation.
This commit is contained in:
@@ -4,10 +4,9 @@
|
|||||||
* This source code is licensed under the MIT license found in the
|
* This source code is licensed under the MIT license found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*
|
*
|
||||||
* @noflow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import StyleSheetValidation from './StyleSheetValidation';
|
|
||||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||||
import flattenStyle from './flattenStyle';
|
import flattenStyle from './flattenStyle';
|
||||||
|
|
||||||
@@ -23,18 +22,34 @@ const absoluteFill = ReactNativePropRegistry.register(absoluteFillObject);
|
|||||||
const StyleSheet = {
|
const StyleSheet = {
|
||||||
absoluteFill,
|
absoluteFill,
|
||||||
absoluteFillObject,
|
absoluteFillObject,
|
||||||
compose(style1, style2) {
|
compose(style1: any, style2: any) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
/* eslint-disable prefer-rest-params */
|
||||||
|
const len = arguments.length;
|
||||||
|
if (len > 2) {
|
||||||
|
const readableStyles = [...arguments].map(a => flattenStyle(a));
|
||||||
|
throw new Error(
|
||||||
|
`StyleSheet.compose() only accepts 2 arguments, received ${len}: ${JSON.stringify(
|
||||||
|
readableStyles
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* eslint-enable prefer-rest-params */
|
||||||
|
}
|
||||||
|
|
||||||
if (style1 && style2) {
|
if (style1 && style2) {
|
||||||
return [style1, style2];
|
return [style1, style2];
|
||||||
} else {
|
} else {
|
||||||
return style1 || style2;
|
return style1 || style2;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
create(styles) {
|
create(styles: Object) {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.keys(styles).forEach(key => {
|
Object.keys(styles).forEach(key => {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
StyleSheetValidation.validateStyle(key, styles);
|
const validate = require('./validate');
|
||||||
|
const interopValidate = validate.default ? validate.default : validate;
|
||||||
|
interopValidate(key, styles);
|
||||||
}
|
}
|
||||||
const id = styles[key] && ReactNativePropRegistry.register(styles[key]);
|
const id = styles[key] && ReactNativePropRegistry.register(styles[key]);
|
||||||
result[key] = id;
|
result[key] = id;
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ImageStylePropTypes from '../Image/ImageStylePropTypes';
|
|
||||||
import TextInputStylePropTypes from '../TextInput/TextInputStylePropTypes';
|
|
||||||
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
|
||||||
import ViewStylePropTypes from '../View/ViewStylePropTypes';
|
|
||||||
import warning from 'fbjs/lib/warning';
|
|
||||||
import { number, oneOf, string } from 'prop-types';
|
|
||||||
|
|
||||||
// Hardcoded because this is a legit case but we don't want to load it from
|
|
||||||
// a private API. We might likely want to unify style sheet creation with how it
|
|
||||||
// is done in the DOM so this might move into React. I know what I'm doing so
|
|
||||||
// plz don't fire me.
|
|
||||||
const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
|
|
||||||
|
|
||||||
class StyleSheetValidation {
|
|
||||||
static validateStyleProp(prop: string, style: Object, caller: string) {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const value = style[prop];
|
|
||||||
|
|
||||||
const isCustomProperty = prop.indexOf('--') === 0;
|
|
||||||
if (isCustomProperty) return;
|
|
||||||
|
|
||||||
if (allStylePropTypes[prop] === undefined) {
|
|
||||||
const message1 = '"' + prop + '" is not a valid style property.';
|
|
||||||
const message2 =
|
|
||||||
'\nValid style props: ' +
|
|
||||||
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ');
|
|
||||||
styleError(message1, style, caller, message2);
|
|
||||||
} else if (typeof value === 'string' && value.indexOf('!important') > -1) {
|
|
||||||
styleError(
|
|
||||||
`Invalid value of "${value}" set on prop "${prop}". Values cannot include "!important"`,
|
|
||||||
style,
|
|
||||||
caller
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const error = allStylePropTypes[prop](
|
|
||||||
style,
|
|
||||||
prop,
|
|
||||||
caller,
|
|
||||||
'prop',
|
|
||||||
null,
|
|
||||||
ReactPropTypesSecret
|
|
||||||
);
|
|
||||||
if (error) {
|
|
||||||
styleError(error.message, style, caller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static validateStyle(name: string, styles: Object) {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
for (const prop in styles[name]) {
|
|
||||||
StyleSheetValidation.validateStyleProp(prop, styles[name], 'StyleSheet ' + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static addValidStylePropTypes(stylePropTypes: Object) {
|
|
||||||
for (const key in stylePropTypes) {
|
|
||||||
allStylePropTypes[key] = stylePropTypes[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const styleError = function(message1, style, caller?, message2?) {
|
|
||||||
warning(
|
|
||||||
false,
|
|
||||||
message1 +
|
|
||||||
'\n' +
|
|
||||||
(caller || '<<unknown>>') +
|
|
||||||
': ' +
|
|
||||||
JSON.stringify(style, null, ' ') +
|
|
||||||
(message2 || '')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const allStylePropTypes = {};
|
|
||||||
|
|
||||||
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes);
|
|
||||||
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes);
|
|
||||||
StyleSheetValidation.addValidStylePropTypes(TextInputStylePropTypes);
|
|
||||||
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes);
|
|
||||||
|
|
||||||
StyleSheetValidation.addValidStylePropTypes({
|
|
||||||
appearance: string,
|
|
||||||
borderCollapse: string,
|
|
||||||
borderSpacing: oneOf([number, string]),
|
|
||||||
clear: string,
|
|
||||||
cursor: string,
|
|
||||||
fill: string,
|
|
||||||
float: oneOf(['end', 'left', 'none', 'right', 'start']),
|
|
||||||
listStyle: string,
|
|
||||||
objectFit: oneOf(['fill', 'contain', 'cover', 'none', 'scale-down']),
|
|
||||||
objectPosition: string,
|
|
||||||
pointerEvents: string,
|
|
||||||
tableLayout: string
|
|
||||||
});
|
|
||||||
|
|
||||||
export default StyleSheetValidation;
|
|
||||||
-3
@@ -114,14 +114,11 @@ to { left: 10px; }
|
|||||||
|
|
||||||
exports[`StyleSheet/compile inline converts style to inline styles 1`] = `
|
exports[`StyleSheet/compile inline converts style to inline styles 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"WebkitFlexBasis": "auto",
|
|
||||||
"WebkitFlexShrink": 1,
|
"WebkitFlexShrink": 1,
|
||||||
"display": "flex",
|
"display": "flex",
|
||||||
"flexBasis": "auto",
|
|
||||||
"flexShrink": 1,
|
"flexShrink": 1,
|
||||||
"marginLeft": "10px",
|
"marginLeft": "10px",
|
||||||
"marginRight": "10px",
|
"marginRight": "10px",
|
||||||
"msFlexNegative": 1,
|
"msFlexNegative": 1,
|
||||||
"msFlexPreferredSize": "auto",
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -23,3 +23,27 @@ export const STYLE_GROUPS = {
|
|||||||
paddingVertical: 2.1
|
paddingVertical: 2.1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const STYLE_SHORT_FORM_EXPANSIONS = {
|
||||||
|
borderColor: ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
||||||
|
borderRadius: [
|
||||||
|
'borderTopLeftRadius',
|
||||||
|
'borderTopRightRadius',
|
||||||
|
'borderBottomRightRadius',
|
||||||
|
'borderBottomLeftRadius'
|
||||||
|
],
|
||||||
|
borderStyle: ['borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle'],
|
||||||
|
borderWidth: ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
||||||
|
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
||||||
|
marginHorizontal: ['marginRight', 'marginLeft'],
|
||||||
|
marginVertical: ['marginTop', 'marginBottom'],
|
||||||
|
overflow: ['overflowX', 'overflowY'],
|
||||||
|
overscrollBehavior: ['overscrollBehaviorX', 'overscrollBehaviorY'],
|
||||||
|
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
||||||
|
paddingHorizontal: ['paddingRight', 'paddingLeft'],
|
||||||
|
paddingVertical: ['paddingTop', 'paddingBottom']
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MONOSPACE_FONT_STACK = 'monospace, monospace';
|
||||||
|
export const SYSTEM_FONT_STACK =
|
||||||
|
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* @noflow
|
* @noflow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MONOSPACE_FONT_STACK, SYSTEM_FONT_STACK, STYLE_SHORT_FORM_EXPANSIONS } from './constants';
|
||||||
import normalizeValueWithProperty from './normalizeValueWithProperty';
|
import normalizeValueWithProperty from './normalizeValueWithProperty';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,30 +22,6 @@ import normalizeValueWithProperty from './normalizeValueWithProperty';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const emptyObject = {};
|
const emptyObject = {};
|
||||||
const styleShortFormProperties = {
|
|
||||||
borderColor: ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
|
||||||
borderRadius: [
|
|
||||||
'borderTopLeftRadius',
|
|
||||||
'borderTopRightRadius',
|
|
||||||
'borderBottomRightRadius',
|
|
||||||
'borderBottomLeftRadius'
|
|
||||||
],
|
|
||||||
borderStyle: ['borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle'],
|
|
||||||
borderWidth: ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
|
||||||
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
|
||||||
marginHorizontal: ['marginRight', 'marginLeft'],
|
|
||||||
marginVertical: ['marginTop', 'marginBottom'],
|
|
||||||
overflow: ['overflowX', 'overflowY'],
|
|
||||||
overscrollBehavior: ['overscrollBehaviorX', 'overscrollBehaviorY'],
|
|
||||||
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
|
||||||
paddingHorizontal: ['paddingRight', 'paddingLeft'],
|
|
||||||
paddingVertical: ['paddingTop', 'paddingBottom'],
|
|
||||||
writingDirection: ['direction']
|
|
||||||
};
|
|
||||||
|
|
||||||
const monospaceFontStack = 'monospace, monospace';
|
|
||||||
const systemFontStack =
|
|
||||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform
|
* Transform
|
||||||
@@ -135,17 +112,17 @@ const createReactDOMStyle = style => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'font': {
|
case 'font': {
|
||||||
resolvedStyle[prop] = value.replace('System', systemFontStack);
|
resolvedStyle[prop] = value.replace('System', SYSTEM_FONT_STACK);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'fontFamily': {
|
case 'fontFamily': {
|
||||||
if (value.indexOf('System') > -1) {
|
if (value.indexOf('System') > -1) {
|
||||||
const stack = value.split(/,\s*/);
|
const stack = value.split(/,\s*/);
|
||||||
stack[stack.indexOf('System')] = systemFontStack;
|
stack[stack.indexOf('System')] = SYSTEM_FONT_STACK;
|
||||||
resolvedStyle[prop] = stack.join(', ');
|
resolvedStyle[prop] = stack.join(', ');
|
||||||
} else if (value === 'monospace') {
|
} else if (value === 'monospace') {
|
||||||
resolvedStyle[prop] = monospaceFontStack;
|
resolvedStyle[prop] = MONOSPACE_FONT_STACK;
|
||||||
} else {
|
} else {
|
||||||
resolvedStyle[prop] = value;
|
resolvedStyle[prop] = value;
|
||||||
}
|
}
|
||||||
@@ -177,8 +154,13 @@ const createReactDOMStyle = style => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'writingDirection': {
|
||||||
|
resolvedStyle.direction = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
const longFormProperties = styleShortFormProperties[prop];
|
const longFormProperties = STYLE_SHORT_FORM_EXPANSIONS[prop];
|
||||||
if (longFormProperties) {
|
if (longFormProperties) {
|
||||||
longFormProperties.forEach((longForm, i) => {
|
longFormProperties.forEach((longForm, i) => {
|
||||||
// The value of any longform property in the original styles takes
|
// The value of any longform property in the original styles takes
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Nicolas Gallagher.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @flow strict-local
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import { STYLE_SHORT_FORM_EXPANSIONS } from './constants';
|
||||||
|
import ImageStylePropTypes from '../Image/ImageStylePropTypes';
|
||||||
|
import TextInputStylePropTypes from '../TextInput/TextInputStylePropTypes';
|
||||||
|
import TextStylePropTypes from '../Text/TextStylePropTypes';
|
||||||
|
import ViewStylePropTypes from '../View/ViewStylePropTypes';
|
||||||
|
import warning from 'fbjs/lib/warning';
|
||||||
|
|
||||||
|
const validProperties = [
|
||||||
|
...Object.keys(ImageStylePropTypes),
|
||||||
|
...Object.keys(TextInputStylePropTypes),
|
||||||
|
...Object.keys(TextStylePropTypes),
|
||||||
|
...Object.keys(ViewStylePropTypes),
|
||||||
|
'appearance',
|
||||||
|
'borderCollapse',
|
||||||
|
'borderSpacing',
|
||||||
|
'clear',
|
||||||
|
'cursor',
|
||||||
|
'fill',
|
||||||
|
'float',
|
||||||
|
'listStyle',
|
||||||
|
'objectFit',
|
||||||
|
'objectPosition',
|
||||||
|
'pointerEvents',
|
||||||
|
'placeholderTextColor',
|
||||||
|
'tableLayout'
|
||||||
|
]
|
||||||
|
.sort()
|
||||||
|
.reduce((acc, curr) => {
|
||||||
|
acc[curr] = true;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const invalidShortforms = {
|
||||||
|
background: true,
|
||||||
|
borderBottom: true,
|
||||||
|
borderLeft: true,
|
||||||
|
borderRight: true,
|
||||||
|
borderTop: true,
|
||||||
|
font: true,
|
||||||
|
grid: true,
|
||||||
|
outline: true,
|
||||||
|
textDecoration: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
const singleValueShortForms = Object.keys(STYLE_SHORT_FORM_EXPANSIONS).reduce((a, c) => {
|
||||||
|
a[c] = true;
|
||||||
|
return a;
|
||||||
|
}, {});
|
||||||
|
*/
|
||||||
|
|
||||||
|
function error(message) {
|
||||||
|
warning(false, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function validate(key: string, styles: Object) {
|
||||||
|
const obj = styles[key];
|
||||||
|
for (const k in obj) {
|
||||||
|
const prop = k.trim();
|
||||||
|
const value = obj[prop];
|
||||||
|
const isValidProp = validProperties[prop];
|
||||||
|
const isInvalidShorthand = invalidShortforms[prop];
|
||||||
|
let isInvalid = false;
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidProp) {
|
||||||
|
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}"?';
|
||||||
|
} else if (prop === 'direction') {
|
||||||
|
suggestion = 'Did you mean "writingDirection"?';
|
||||||
|
} else if (prop === 'verticalAlign') {
|
||||||
|
suggestion = 'Did you mean "textAlignVertical"?';
|
||||||
|
} else if (isInvalidShorthand) {
|
||||||
|
suggestion = 'Please use long-form properties.';
|
||||||
|
}
|
||||||
|
isInvalid = true;
|
||||||
|
error(`Invalid style property of "${prop}". ${suggestion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else if (singleValueShortForms[prop]) {
|
||||||
|
// TODO: fix check
|
||||||
|
// if (typeof value === 'string' && value.indexOf(' ') > -1) {
|
||||||
|
// error(
|
||||||
|
// `Invalid style declaration "${prop}:${value}". This property must only specify a single value.`
|
||||||
|
// );
|
||||||
|
// isInvalid = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
else if (typeof value === 'string' && value.indexOf('!important') > -1) {
|
||||||
|
error(`Invalid style declaration "${prop}:${value}". Values cannot include "!important"`);
|
||||||
|
isInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInvalid) {
|
||||||
|
delete obj[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user