mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-26 15:58:28 +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
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import StyleSheetValidation from './StyleSheetValidation';
|
||||
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
|
||||
import flattenStyle from './flattenStyle';
|
||||
|
||||
@@ -23,18 +22,34 @@ const absoluteFill = ReactNativePropRegistry.register(absoluteFillObject);
|
||||
const StyleSheet = {
|
||||
absoluteFill,
|
||||
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) {
|
||||
return [style1, style2];
|
||||
} else {
|
||||
return style1 || style2;
|
||||
}
|
||||
},
|
||||
create(styles) {
|
||||
create(styles: Object) {
|
||||
const result = {};
|
||||
Object.keys(styles).forEach(key => {
|
||||
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]);
|
||||
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`] = `
|
||||
Object {
|
||||
"WebkitFlexBasis": "auto",
|
||||
"WebkitFlexShrink": 1,
|
||||
"display": "flex",
|
||||
"flexBasis": "auto",
|
||||
"flexShrink": 1,
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "10px",
|
||||
"msFlexNegative": 1,
|
||||
"msFlexPreferredSize": "auto",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -23,3 +23,27 @@ export const STYLE_GROUPS = {
|
||||
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
|
||||
*/
|
||||
|
||||
import { MONOSPACE_FONT_STACK, SYSTEM_FONT_STACK, STYLE_SHORT_FORM_EXPANSIONS } from './constants';
|
||||
import normalizeValueWithProperty from './normalizeValueWithProperty';
|
||||
|
||||
/**
|
||||
@@ -21,30 +22,6 @@ import normalizeValueWithProperty from './normalizeValueWithProperty';
|
||||
*/
|
||||
|
||||
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
|
||||
@@ -135,17 +112,17 @@ const createReactDOMStyle = style => {
|
||||
}
|
||||
|
||||
case 'font': {
|
||||
resolvedStyle[prop] = value.replace('System', systemFontStack);
|
||||
resolvedStyle[prop] = value.replace('System', SYSTEM_FONT_STACK);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'fontFamily': {
|
||||
if (value.indexOf('System') > -1) {
|
||||
const stack = value.split(/,\s*/);
|
||||
stack[stack.indexOf('System')] = systemFontStack;
|
||||
stack[stack.indexOf('System')] = SYSTEM_FONT_STACK;
|
||||
resolvedStyle[prop] = stack.join(', ');
|
||||
} else if (value === 'monospace') {
|
||||
resolvedStyle[prop] = monospaceFontStack;
|
||||
resolvedStyle[prop] = MONOSPACE_FONT_STACK;
|
||||
} else {
|
||||
resolvedStyle[prop] = value;
|
||||
}
|
||||
@@ -177,8 +154,13 @@ const createReactDOMStyle = style => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'writingDirection': {
|
||||
resolvedStyle.direction = value;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
const longFormProperties = styleShortFormProperties[prop];
|
||||
const longFormProperties = STYLE_SHORT_FORM_EXPANSIONS[prop];
|
||||
if (longFormProperties) {
|
||||
longFormProperties.forEach((longForm, i) => {
|
||||
// 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