diff --git a/docs/storybook/1-components/View/ViewScreen.js b/docs/storybook/1-components/View/ViewScreen.js
index 890e63f3..32be8b6b 100644
--- a/docs/storybook/1-components/View/ViewScreen.js
+++ b/docs/storybook/1-components/View/ViewScreen.js
@@ -14,6 +14,7 @@ import UIExplorer, {
Code,
Description,
DocItem,
+ ExternalLink,
Section,
StyleList
} from '../../ui-explorer';
@@ -291,6 +292,12 @@ const ViewScreen = () =>
;
const stylePropTypes = [
+ {
+ label: 'web',
+ name: (
+ Custom properties
+ )
+ },
{
name: 'alignContent',
typeInfo: 'string'
diff --git a/docs/storybook/ui-explorer/ExternalLink.js b/docs/storybook/ui-explorer/ExternalLink.js
new file mode 100644
index 00000000..587b64d1
--- /dev/null
+++ b/docs/storybook/ui-explorer/ExternalLink.js
@@ -0,0 +1,12 @@
+/* eslint-disable react/prop-types */
+
+/**
+ * @flow
+ */
+
+import AppText from './AppText';
+import React from 'react';
+
+const ExternalLink = props => ;
+
+export default ExternalLink;
diff --git a/docs/storybook/ui-explorer/StyleList.js b/docs/storybook/ui-explorer/StyleList.js
index 574a0e98..085857b2 100644
--- a/docs/storybook/ui-explorer/StyleList.js
+++ b/docs/storybook/ui-explorer/StyleList.js
@@ -16,10 +16,12 @@ const StyleList = ({ stylePropTypes }) =>
{name}
- {': '}
-
- {typeInfo}
-
+ {typeInfo ? ': ' : null}
+ {typeInfo
+ ?
+ {typeInfo}
+
+ : null}
)}
;
diff --git a/docs/storybook/ui-explorer/UIExplorer.js b/docs/storybook/ui-explorer/UIExplorer.js
index 00097c3b..ff0c1018 100644
--- a/docs/storybook/ui-explorer/UIExplorer.js
+++ b/docs/storybook/ui-explorer/UIExplorer.js
@@ -5,6 +5,7 @@
*/
import AppText from './AppText';
+import ExternalLink from './ExternalLink';
import insertBetween from './insertBetween';
import React from 'react';
import { StyleSheet, View } from 'react-native';
@@ -22,14 +23,12 @@ export const Description = ({ children }) =>
const Divider = () => ;
const SourceLink = ({ uri }) =>
-
View source code on GitHub
- ;
+ ;
const UIExplorer = ({ children, description, sections, title, url }) =>
diff --git a/docs/storybook/ui-explorer/index.js b/docs/storybook/ui-explorer/index.js
index 5a5e5d30..3bef8a88 100644
--- a/docs/storybook/ui-explorer/index.js
+++ b/docs/storybook/ui-explorer/index.js
@@ -5,10 +5,11 @@
import AppText from './AppText';
import Code from './Code';
import DocItem from './DocItem';
+import ExternalLink from './ExternalLink';
import Section from './Section';
import StyleList from './StyleList';
import TextList from './TextList';
import UIExplorer, { Description } from './UIExplorer';
export default UIExplorer;
-export { AppText, Code, Description, DocItem, Section, StyleList, TextList };
+export { AppText, Code, Description, DocItem, ExternalLink, Section, StyleList, TextList };
diff --git a/src/apis/AppState/index.js b/src/apis/AppState/index.js
index 52880c04..f3eef3d5 100644
--- a/src/apis/AppState/index.js
+++ b/src/apis/AppState/index.js
@@ -15,7 +15,8 @@ import findIndex from 'array-find-index';
import invariant from 'fbjs/lib/invariant';
// Android 4.4 browser
-const isPrefixed = canUseDOM && !document.hasOwnProperty('hidden') && document.hasOwnProperty('webkitHidden');
+const isPrefixed =
+ canUseDOM && !document.hasOwnProperty('hidden') && document.hasOwnProperty('webkitHidden');
const EVENT_TYPES = ['change'];
const VISIBILITY_CHANGE_EVENT = isPrefixed ? 'webkitvisibilitychange' : 'visibilitychange';
diff --git a/src/apis/StyleSheet/StyleSheetValidation.js b/src/apis/StyleSheet/StyleSheetValidation.js
index 1e29d7b8..07de6958 100644
--- a/src/apis/StyleSheet/StyleSheetValidation.js
+++ b/src/apis/StyleSheet/StyleSheetValidation.js
@@ -26,23 +26,27 @@ const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
export default class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') {
+ 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);
- }
- const error = allStylePropTypes[prop](
- style,
- prop,
- caller,
- 'prop',
- null,
- ReactPropTypesSecret
- );
- if (error) {
- styleError(error.message, style, caller);
+ } else {
+ const error = allStylePropTypes[prop](
+ style,
+ prop,
+ caller,
+ 'prop',
+ null,
+ ReactPropTypesSecret
+ );
+ if (error) {
+ styleError(error.message, style, caller);
+ }
}
}
}
diff --git a/src/propTypes/ColorPropType.js b/src/propTypes/ColorPropType.js
index c0335689..07df65b6 100644
--- a/src/propTypes/ColorPropType.js
+++ b/src/propTypes/ColorPropType.js
@@ -35,7 +35,8 @@ const colorPropType = function(isRequired, props, propName, componentName, locat
return;
}
- if (color === 'currentcolor' || color === 'inherit') {
+ // Web supports additional color keywords and custom property values
+ if (color === 'currentcolor' || color === 'inherit' || color.indexOf('var(') === 0) {
return;
}
diff --git a/src/propTypes/StyleSheetPropType.js b/src/propTypes/StyleSheetPropType.js
index b29d49f8..0e1b661e 100644
--- a/src/propTypes/StyleSheetPropType.js
+++ b/src/propTypes/StyleSheetPropType.js
@@ -19,7 +19,15 @@ function StyleSheetPropType(shape: { [key: string]: ReactPropsCheckType }): Reac
if (props[propName]) {
// Just make a dummy prop object with only the flattened style
newProps = {};
- newProps[propName] = StyleSheet.flatten(props[propName]);
+ const flatStyle = StyleSheet.flatten(props[propName]);
+ // Remove custom properties from check
+ const nextStyle = Object.keys(flatStyle).reduce((acc, curr) => {
+ if (curr.indexOf('--') !== 0) {
+ acc[curr] = flatStyle[curr];
+ }
+ return acc;
+ }, {});
+ newProps[propName] = nextStyle;
}
return shapePropType(newProps, propName, componentName, location, ...rest);
};
diff --git a/src/vendor/dangerousStyleValue/index.js b/src/vendor/dangerousStyleValue/index.js
new file mode 100644
index 00000000..710c156c
--- /dev/null
+++ b/src/vendor/dangerousStyleValue/index.js
@@ -0,0 +1,53 @@
+/* eslint-disable */
+
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule dangerousStyleValue
+ */
+
+import isUnitlessNumber from '../../modules/unitlessNumbers';
+
+/**
+ * Convert a value into the proper css writable value. The style name `name`
+ * should be logical (no hyphens), as specified
+ * in `CSSProperty.isUnitlessNumber`.
+ *
+ * @param {string} name CSS property name such as `topMargin`.
+ * @param {*} value CSS property value such as `10px`.
+ * @return {string} Normalized style value with dimensions applied.
+ */
+function dangerousStyleValue(name, value, isCustomProperty) {
+ // Note that we've removed escapeTextForBrowser() calls here since the
+ // whole string will be escaped when the attribute is injected into
+ // the markup. If you provide unsafe user data here they can inject
+ // arbitrary CSS which may be problematic (I couldn't repro this):
+ // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
+ // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
+ // This is not an XSS hole but instead a potential CSS injection issue
+ // which has lead to a greater discussion about how we're going to
+ // trust URLs moving forward. See #2115901
+
+ var isEmpty = value == null || typeof value === 'boolean' || value === '';
+ if (isEmpty) {
+ return '';
+ }
+
+ if (
+ !isCustomProperty &&
+ typeof value === 'number' &&
+ value !== 0 &&
+ !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
+ ) {
+ return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
+ }
+
+ return ('' + value).trim();
+}
+
+export default dangerousStyleValue;
diff --git a/src/vendor/setValueForStyles/index.js b/src/vendor/setValueForStyles/index.js
index dbc9131e..a88177f3 100644
--- a/src/vendor/setValueForStyles/index.js
+++ b/src/vendor/setValueForStyles/index.js
@@ -10,199 +10,8 @@
*
*/
-import unitlessNumbers from '../../modules/unitlessNumbers';
-
-if (process.env.NODE_ENV !== 'production') {
- var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
- var warning = require('fbjs/lib/warning');
-
- // 'msTransform' is correct, but the other prefixes should be capitalized
- var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
-
- // style values shouldn't contain a semicolon
- var badStyleValueWithSemicolonPattern = /;\s*$/;
-
- var warnedStyleNames = {};
- var warnedStyleValues = {};
- var warnedForNaNValue = false;
-
- var warnHyphenatedStyleName = function(name, owner) {
- if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
- return;
- }
-
- warnedStyleNames[name] = true;
- process.env.NODE_ENV !== 'production'
- ? warning(
- false,
- 'Unsupported style property %s. Did you mean %s?%s',
- name,
- camelizeStyleName(name),
- checkRenderMessage(owner)
- )
- : void 0;
- };
-
- var warnBadVendoredStyleName = function(name, owner) {
- if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
- return;
- }
-
- warnedStyleNames[name] = true;
- process.env.NODE_ENV !== 'production'
- ? warning(
- false,
- 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
- name,
- name.charAt(0).toUpperCase() + name.slice(1),
- checkRenderMessage(owner)
- )
- : void 0;
- };
-
- var warnStyleValueWithSemicolon = function(name, value, owner) {
- if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
- return;
- }
-
- warnedStyleValues[value] = true;
- process.env.NODE_ENV !== 'production'
- ? warning(
- false,
- "Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.',
- checkRenderMessage(owner),
- name,
- value.replace(badStyleValueWithSemicolonPattern, '')
- )
- : void 0;
- };
-
- var warnStyleValueIsNaN = function(name, value, owner) {
- if (warnedForNaNValue) {
- return;
- }
-
- warnedForNaNValue = true;
- process.env.NODE_ENV !== 'production'
- ? warning(
- false,
- '`NaN` is an invalid value for the `%s` css style property.%s',
- name,
- checkRenderMessage(owner)
- )
- : void 0;
- };
-
- var checkRenderMessage = function(owner) {
- if (owner) {
- var name = owner.getName();
- if (name) {
- return ' Check the render method of `' + name + '`.';
- }
- }
- return '';
- };
-
- /**
- * @param {string} name
- * @param {*} value
- * @param {ReactDOMComponent} component
- */
- var warnValidStyle = function(name, value, component) {
- var owner;
- if (component) {
- owner = component._currentElement._owner;
- }
- if (name.indexOf('-') > -1) {
- warnHyphenatedStyleName(name, owner);
- } else if (badVendoredStyleNamePattern.test(name)) {
- warnBadVendoredStyleName(name, owner);
- } else if (badStyleValueWithSemicolonPattern.test(value)) {
- warnStyleValueWithSemicolon(name, value, owner);
- }
-
- if (typeof value === 'number' && isNaN(value)) {
- warnStyleValueIsNaN(name, value, owner);
- }
- };
-}
-
-var styleWarnings = {};
-
-/**
- * Convert a value into the proper css writable value. The style name `name`
- * should be logical (no hyphens)
- *
- * @param {string} name CSS property name such as `topMargin`.
- * @param {*} value CSS property value such as `10px`.
- * @param {ReactDOMComponent} component
- * @return {string} Normalized style value with dimensions applied.
- */
-function dangerousStyleValue(name, value, component) {
- // Note that we've removed escapeTextForBrowser() calls here since the
- // whole string will be escaped when the attribute is injected into
- // the markup. If you provide unsafe user data here they can inject
- // arbitrary CSS which may be problematic (I couldn't repro this):
- // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
- // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
- // This is not an XSS hole but instead a potential CSS injection issue
- // which has lead to a greater discussion about how we're going to
- // trust URLs moving forward. See #2115901
-
- var isEmpty = value == null || typeof value === 'boolean' || value === '';
- if (isEmpty) {
- return '';
- }
-
- var isNonNumeric = isNaN(value);
- if (
- isNonNumeric ||
- value === 0 ||
- (unitlessNumbers.hasOwnProperty(name) && unitlessNumbers[name])
- ) {
- return '' + value; // cast to string
- }
-
- if (typeof value === 'string') {
- if (process.env.NODE_ENV !== 'production') {
- var warning = require('fbjs/lib/warning');
-
- // Allow '0' to pass through without warning. 0 is already special and
- // doesn't require units, so we don't need to warn about it.
- if (component && value !== '0') {
- var owner = component._currentElement._owner;
- var ownerName = owner ? owner.getName() : null;
- if (ownerName && !styleWarnings[ownerName]) {
- styleWarnings[ownerName] = {};
- }
- var warned = false;
- if (ownerName) {
- var warnings = styleWarnings[ownerName];
- warned = warnings[name];
- if (!warned) {
- warnings[name] = true;
- }
- }
- if (!warned) {
- process.env.NODE_ENV !== 'production'
- ? warning(
- false,
- 'a `%s` tag (owner: `%s`) was passed a numeric string value ' +
- 'for CSS property `%s` (value: `%s`) which will be treated ' +
- 'as a unitless number in a future version of React.',
- component._currentElement.type,
- ownerName || 'unknown',
- name,
- value
- )
- : void 0;
- }
- }
- }
- value = value.trim();
- }
- return value + 'px';
-}
+import dangerousStyleValue from '../dangerousStyleValue';
+import warnValidStyle from '../warnValidStyle';
/**
* Sets the value for multiple styles on a node. If a value is specified as
@@ -218,14 +27,19 @@ const setValueForStyles = function(node, styles, component) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
+ var isCustomProperty = styleName.indexOf('--') === 0;
if (process.env.NODE_ENV !== 'production') {
- warnValidStyle(styleName, styles[styleName], component);
+ if (!isCustomProperty) {
+ warnValidStyle(styleName, styles[styleName], component);
+ }
}
- var styleValue = dangerousStyleValue(styleName, styles[styleName], component);
- if (styleName === 'float' || styleName === 'cssFloat') {
+ var styleValue = dangerousStyleValue(styleName, styles[styleName], isCustomProperty);
+ if (styleName === 'float') {
styleName = 'cssFloat';
}
- if (styleValue) {
+ if (isCustomProperty) {
+ style.setProperty(styleName, styleValue);
+ } else if (styleValue) {
style[styleName] = styleValue;
} else {
style[styleName] = '';
diff --git a/src/vendor/warnValidStyle/index.js b/src/vendor/warnValidStyle/index.js
new file mode 100644
index 00000000..23e2c44a
--- /dev/null
+++ b/src/vendor/warnValidStyle/index.js
@@ -0,0 +1,170 @@
+/* eslint-disable */
+
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule warnValidStyle
+ */
+
+'use strict';
+
+var emptyFunction = require('fbjs/lib/emptyFunction');
+
+var warnValidStyle = emptyFunction;
+
+if (process.env.NODE_ENV !== 'production') {
+ var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
+ var warning = require('fbjs/lib/warning');
+
+ function getComponentName(instanceOrFiber) {
+ if (typeof instanceOrFiber.getName === 'function') {
+ // Stack reconciler
+ const instance = ((instanceOrFiber: any): ReactInstance);
+ return instance.getName();
+ }
+ if (typeof instanceOrFiber.tag === 'number') {
+ // Fiber reconciler
+ const fiber = ((instanceOrFiber: any): Fiber);
+ const { type } = fiber;
+ if (typeof type === 'string') {
+ return type;
+ }
+ if (typeof type === 'function') {
+ return type.displayName || type.name;
+ }
+ }
+ return null;
+ }
+
+ // 'msTransform' is correct, but the other prefixes should be capitalized
+ var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
+
+ // style values shouldn't contain a semicolon
+ var badStyleValueWithSemicolonPattern = /;\s*$/;
+
+ var warnedStyleNames = {};
+ var warnedStyleValues = {};
+ var warnedForNaNValue = false;
+ var warnedForInfinityValue = false;
+
+ var warnHyphenatedStyleName = function(name, owner) {
+ if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
+ return;
+ }
+
+ warnedStyleNames[name] = true;
+ warning(
+ false,
+ 'Unsupported style property %s. Did you mean %s?%s',
+ name,
+ camelizeStyleName(name),
+ checkRenderMessage(owner)
+ );
+ };
+
+ var warnBadVendoredStyleName = function(name, owner) {
+ if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
+ return;
+ }
+
+ warnedStyleNames[name] = true;
+ warning(
+ false,
+ 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
+ name,
+ name.charAt(0).toUpperCase() + name.slice(1),
+ checkRenderMessage(owner)
+ );
+ };
+
+ var warnStyleValueWithSemicolon = function(name, value, owner) {
+ if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
+ return;
+ }
+
+ warnedStyleValues[value] = true;
+ warning(
+ false,
+ "Style property values shouldn't contain a semicolon.%s " + 'Try "%s: %s" instead.',
+ checkRenderMessage(owner),
+ name,
+ value.replace(badStyleValueWithSemicolonPattern, '')
+ );
+ };
+
+ var warnStyleValueIsNaN = function(name, value, owner) {
+ if (warnedForNaNValue) {
+ return;
+ }
+
+ warnedForNaNValue = true;
+ warning(
+ false,
+ '`NaN` is an invalid value for the `%s` css style property.%s',
+ name,
+ checkRenderMessage(owner)
+ );
+ };
+
+ var warnStyleValueIsInfinity = function(name, value, owner) {
+ if (warnedForInfinityValue) {
+ return;
+ }
+
+ warnedForInfinityValue = true;
+ warning(
+ false,
+ '`Infinity` is an invalid value for the `%s` css style property.%s',
+ name,
+ checkRenderMessage(owner)
+ );
+ };
+
+ var checkRenderMessage = function(owner) {
+ var ownerName;
+ if (owner != null) {
+ // Stack passes the owner manually all the way to CSSPropertyOperations.
+ ownerName = getComponentName(owner);
+ } else {
+ // Fiber doesn't pass it but uses ReactDebugCurrentFiber to track it.
+ // It is only enabled in development and tracks host components too.
+ // var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber');
+ // ownerName = getCurrentFiberOwnerName();
+ // TODO: also report the stack.
+ }
+ if (ownerName) {
+ return '\n\nCheck the render method of `' + ownerName + '`.';
+ }
+ return '';
+ };
+
+ warnValidStyle = function(name, value, component) {
+ var owner;
+ if (component) {
+ // TODO: this only works with Stack. Seems like we need to add unit tests?
+ // owner = component._currentElement._owner;
+ }
+ if (name.indexOf('-') > -1) {
+ warnHyphenatedStyleName(name, owner);
+ } else if (badVendoredStyleNamePattern.test(name)) {
+ warnBadVendoredStyleName(name, owner);
+ } else if (badStyleValueWithSemicolonPattern.test(value)) {
+ warnStyleValueWithSemicolon(name, value, owner);
+ }
+
+ if (typeof value === 'number') {
+ if (isNaN(value)) {
+ warnStyleValueIsNaN(name, value, owner);
+ } else if (!isFinite(value)) {
+ warnStyleValueIsInfinity(name, value, owner);
+ }
+ }
+ };
+}
+
+export default warnValidStyle;