diff --git a/src/apis/StyleSheet/StyleManager.js b/src/apis/StyleSheet/StyleManager.js
new file mode 100644
index 00000000..ed0a8ebe
--- /dev/null
+++ b/src/apis/StyleSheet/StyleManager.js
@@ -0,0 +1,127 @@
+import asap from 'asap';
+import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
+import generateCss from './generateCss';
+import hash from './hash';
+import staticCss from './staticCss';
+
+const emptyObject = {};
+const STYLE_ELEMENT_ID = 'react-native-stylesheet';
+
+const createClassName = (prop, value) => {
+ const hashed = hash(prop + value);
+ return process.env.NODE_ENV !== 'production' ? `rn-${prop}-${hashed}` : `rn-${hashed}`;
+};
+
+const createCssRule = (className, prop, value) => {
+ const css = generateCss({ [prop]: value });
+ const selector = `.${className}`;
+ return `${selector}{${css}}`;
+};
+
+const pointerEvents = {
+ auto: createClassName('pointerEvents', 'auto'),
+ boxNone: createClassName('pointerEvents', 'box-none'),
+ boxOnly: createClassName('pointerEvents', 'box-only'),
+ none: createClassName('pointerEvents', 'none')
+};
+
+const pointerEventsCss =
+ `.${pointerEvents.auto}{pointer-events:auto;}\n` +
+ `.${pointerEvents.boxNone}{pointer-events:none;}\n` +
+ `.${pointerEvents.boxNone} *{pointer-events:auto;}\n` +
+ `.${pointerEvents.boxOnly}{pointer-events:auto;}\n` +
+ `.${pointerEvents.boxOnly} *{pointer-events:none;}\n` +
+ `.${pointerEvents.none}{pointer-events:none;}`;
+
+class StyleManager {
+ constructor() {
+ // custom pointer event values are implemented using descendent selectors,
+ // so we manually create the CSS and pre-register the declarations
+ const pointerEventsPropName = 'pointerEvents';
+ this.cache = {
+ byClassName: {
+ [pointerEvents.auto]: { prop: pointerEventsPropName, value: 'auto' },
+ [pointerEvents.boxNone]: { prop: pointerEventsPropName, value: 'box-none' },
+ [pointerEvents.boxOnly]: { prop: pointerEventsPropName, value: 'box-only' },
+ [pointerEvents.none]: { prop: pointerEventsPropName, value: 'none' }
+ },
+ byProp: {
+ pointerEvents: {
+ 'auto': pointerEvents.auto,
+ 'box-none': pointerEvents.boxNone,
+ 'box-only': pointerEvents.boxOnly,
+ 'none': pointerEvents.none
+ }
+ }
+ };
+
+ // on the client we check for an existing style sheet before injecting style sheets
+ if (canUseDOM) {
+ const prerenderedStyleSheet = document.getElementById(STYLE_ELEMENT_ID);
+ if (prerenderedStyleSheet) {
+ this.mainSheet = prerenderedStyleSheet;
+ } else {
+ document.head.insertAdjacentHTML('afterbegin', this.getStyleSheetHtml());
+ this.mainSheet = document.getElementById(STYLE_ELEMENT_ID);
+ }
+ }
+ }
+
+ getClassName(prop, value) {
+ const cache = this.cache.byProp;
+ return cache[prop] && cache[prop].hasOwnProperty(value) && cache[prop][value];
+ }
+
+ getDeclaration(className) {
+ const cache = this.cache.byClassName;
+ return cache[className] || emptyObject;
+ }
+
+ getStyleSheetHtml() {
+ const cache = this.cache.byProp;
+
+ const mainSheetTextContext = Object.keys(cache).reduce((rules, prop) => {
+ if (prop !== 'pointerEvents') {
+ Object.keys(cache[prop]).forEach((value) => {
+ const className = this.getClassName(prop, value);
+ rules.push(createCssRule(className, prop, value));
+ });
+ }
+ return rules;
+ }, []).join('\n');
+
+ const staticSheet = ``;
+ const mainSheet = ``;
+ return `${staticSheet}\n${mainSheet}`;
+ }
+
+ setDeclaration(prop, value) {
+ let className = this.getClassName(prop, value);
+ if (!className) {
+ className = createClassName(prop, value);
+ this._addToCache(className, prop, value);
+ if (canUseDOM) {
+ asap(() => {
+ const sheet = this.mainSheet.sheet;
+ // avoid injecting if the rule already exists (e.g., server rendered, hot reload)
+ if (this.mainSheet.textContent.indexOf(className) === -1) {
+ const rule = createCssRule(className, prop, value);
+ sheet.insertRule(rule, sheet.cssRules.length);
+ }
+ });
+ }
+ }
+ return className;
+ }
+
+ _addToCache(className, prop, value) {
+ const cache = this.cache;
+ if (!cache.byProp[prop]) {
+ cache.byProp[prop] = {};
+ }
+ cache.byProp[prop][value] = className;
+ cache.byClassName[className] = { prop, value };
+ }
+}
+
+module.exports = StyleManager;
diff --git a/src/apis/StyleSheet/StyleRegistry.js b/src/apis/StyleSheet/StyleRegistry.js
new file mode 100644
index 00000000..57507cb9
--- /dev/null
+++ b/src/apis/StyleSheet/StyleRegistry.js
@@ -0,0 +1,158 @@
+/**
+ * WARNING: changes to this file in particular can cause significant changes to
+ * the results of render performance benchmarks.
+ */
+import createReactDOMStyle from './createReactDOMStyle';
+import flattenArray from '../../modules/flattenArray';
+import flattenStyle from './flattenStyle';
+import I18nManager from '../I18nManager';
+import mapKeyValue from '../../modules/mapKeyValue';
+import prefixInlineStyles from './prefixInlineStyles';
+import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
+import StyleManager from './StyleManager';
+
+const createCacheKey = (id) => {
+ const prefix = I18nManager.isRTL ? 'rtl' : 'ltr';
+ return `${prefix}-${id}`;
+};
+
+const classListToString = (list) => list.join(' ').trim();
+
+class StyleRegistry {
+ constructor() {
+ this.cache = {};
+ this.styleManager = new StyleManager();
+ }
+
+ getStyleSheetHtml() {
+ return this.styleManager.getStyleSheetHtml();
+ }
+
+ /**
+ * Registers and precaches a React Native style object to HTML class names
+ */
+ register(flatStyle) {
+ const id = ReactNativePropRegistry.register(flatStyle);
+ const key = createCacheKey(id);
+ const style = createReactDOMStyle(flatStyle);
+ const classList = mapKeyValue(style, (prop, value) => {
+ if (value != null) {
+ return this.styleManager.setDeclaration(prop, value);
+ }
+ });
+ const className = classList.join(' ').trim();
+ this.cache[key] = { classList, className };
+ return id;
+ }
+
+ /**
+ * Resolves a React Native style object to DOM attributes
+ */
+ resolve(reactNativeStyle) {
+ if (!reactNativeStyle) {
+ return undefined;
+ }
+
+ // fast and cachable
+ if (typeof reactNativeStyle === 'number') {
+ const key = createCacheKey(reactNativeStyle);
+ return this._resolveStyleIfNeeded(key, reactNativeStyle);
+ }
+
+ // resolve a plain RN style object
+ if (!Array.isArray(reactNativeStyle)) {
+ return this._resolveStyle(reactNativeStyle);
+ }
+
+ // flatten the style array
+ // cache resolved props when all styles are registered
+ // otherwise fallback to resolving
+ const flatArray = flattenArray(reactNativeStyle);
+ let isArrayOfNumbers = true;
+ for (let i = 0; i < flatArray.length; i++) {
+ if (typeof flatArray[i] !== 'number') {
+ isArrayOfNumbers = false;
+ break;
+ }
+ }
+ const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
+ return this._resolveStyleIfNeeded(key, flatArray);
+ }
+
+ /**
+ * Resolves a React Native style object to DOM attributes, accounting for
+ * the existing styles applied to the DOM node
+ */
+ resolveStateful(reactNativeStyle, domClassList) {
+ const previousReactNativeStyle = {};
+ const preservedClassNames = [];
+
+ // Convert the existing classList to a React Native style and preserve any
+ // unrecognized classNames.
+ domClassList.forEach((className) => {
+ const { prop, value } = this.styleManager.getDeclaration(className);
+ if (prop) {
+ previousReactNativeStyle[prop] = value;
+ } else {
+ preservedClassNames.push(className);
+ }
+ });
+
+ // Resolve the two React Native styles.
+ const { classList, style = {} } = this.resolve([ previousReactNativeStyle, reactNativeStyle ]);
+
+ // Because this is used in stateful operations we need to remove any
+ // existing inline styles that would override the classNames.
+ classList.forEach((className) => {
+ const { prop } = this.styleManager.getDeclaration(className);
+ style[prop] = null;
+ });
+
+ classList.push(preservedClassNames);
+
+ const className = classListToString(classList);
+ return { className, style };
+ }
+
+ /**
+ * Resolves a React Native style object
+ */
+ _resolveStyle(reactNativeStyle) {
+ const domStyle = createReactDOMStyle(flattenStyle(reactNativeStyle));
+
+ const props = Object.keys(domStyle).reduce((props, styleProp) => {
+ const value = domStyle[styleProp];
+ if (value != null) {
+ const className = this.styleManager.getClassName(styleProp, value);
+ if (className) {
+ props.classList.push(className);
+ } else {
+ // 4x slower render
+ props.style[styleProp] = value;
+ }
+ }
+ return props;
+ }, { classList: [], style: {} });
+
+ const style = prefixInlineStyles(props.style);
+ props.className = classListToString(props.classList);
+ props.style = style;
+ return props;
+ }
+
+ /**
+ * Caching layer over 'resolveStyle'
+ */
+ _resolveStyleIfNeeded(key, style) {
+ if (key) {
+ if (!this.cache[key]) {
+ // slow: convert style object to props and cache
+ this.cache[key] = this._resolveStyle(style);
+ }
+ return this.cache[key];
+ }
+ return this._resolveStyle(style);
+ }
+}
+
+module.exports = StyleRegistry;
diff --git a/src/apis/StyleSheet/__tests__/StyleManager-test.js b/src/apis/StyleSheet/__tests__/StyleManager-test.js
new file mode 100644
index 00000000..fc07d905
--- /dev/null
+++ b/src/apis/StyleSheet/__tests__/StyleManager-test.js
@@ -0,0 +1,28 @@
+/* eslint-env jasmine, jest */
+
+import StyleManager from '../StyleManager';
+
+let styleManager;
+
+describe('apis/StyleSheet/StyleManager', () => {
+ beforeEach(() => {
+ styleManager = new StyleManager();
+ });
+
+ test('getClassName', () => {
+ expect(styleManager.getClassName('pointerEvents', 'box-only')).toMatchSnapshot();
+ const className = styleManager.setDeclaration('width', '100px');
+ expect(styleManager.getClassName('width', '100px')).toEqual(className);
+ });
+
+ test('getDeclaration', () => {
+ const className = styleManager.setDeclaration('width', '100px');
+ expect(styleManager.getDeclaration(className)).toEqual({ prop: 'width', value: '100px' });
+ });
+
+ test('getStyleSheetHtml', () => {
+ expect(styleManager.getStyleSheetHtml()).toMatchSnapshot();
+ styleManager.setDeclaration('width', '100px');
+ expect(styleManager.getStyleSheetHtml()).toMatchSnapshot();
+ });
+});
diff --git a/src/apis/StyleSheet/__tests__/StyleRegistry-test.js b/src/apis/StyleSheet/__tests__/StyleRegistry-test.js
new file mode 100644
index 00000000..41bf0543
--- /dev/null
+++ b/src/apis/StyleSheet/__tests__/StyleRegistry-test.js
@@ -0,0 +1,68 @@
+/* eslint-env jasmine, jest */
+
+import StyleRegistry from '../StyleRegistry';
+
+let styleRegistry;
+
+describe('apis/StyleSheet/StyleRegistry', () => {
+ beforeEach(() => {
+ styleRegistry = new StyleRegistry();
+ });
+
+ test('register', () => {
+ const style = { opacity: 0 };
+ const id = styleRegistry.register(style);
+ expect(typeof id === 'number').toBe(true);
+ });
+
+ describe('resolve', () => {
+ const styleA = { borderWidth: 0, borderColor: 'red', width: 100 };
+ const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' };
+ const styleC = { width: 200 };
+
+ const testResolve = (a, b, c) => {
+ // no common properties, different resolving order, same result
+ const resolve1 = styleRegistry.resolve([ a, b ]);
+ expect(resolve1).toMatchSnapshot();
+ const resolve2 = styleRegistry.resolve([ b, a ]);
+ expect(resolve1).toEqual(resolve2);
+
+ // common properties, different resolving order, different result
+ const resolve3 = styleRegistry.resolve([ a, b, c ]);
+ expect(resolve3).toMatchSnapshot();
+ const resolve4 = styleRegistry.resolve([ c, a, b ]);
+ expect(resolve4).toMatchSnapshot();
+ expect(resolve3).not.toEqual(resolve4);
+ };
+
+ test('with register, resolves to className', () => {
+ const a = styleRegistry.register(styleA);
+ const b = styleRegistry.register(styleB);
+ const c = styleRegistry.register(styleC);
+ testResolve(a, b, c);
+ });
+
+ test('with register, resolves to mixed', () => {
+ const a = styleA;
+ const b = styleRegistry.register(styleB);
+ const c = styleRegistry.register(styleC);
+ testResolve(a, b, c);
+ });
+
+ test('without register, resolves to inline styles', () => {
+ testResolve(styleA, styleB, styleC);
+ });
+ });
+
+ test('resolveStateful', () => {
+ // generate a classList to act as pre-existing DOM state
+ const mockStyle = styleRegistry.register({ borderWidth: 0, borderColor: 'red', width: 100 });
+ const { classList: domClassList } = styleRegistry.resolve(mockStyle);
+ domClassList.unshift('external-className');
+ expect(domClassList).toMatchSnapshot();
+
+ // test result
+ const result = styleRegistry.resolveStateful({ borderWidth: 1, opacity: 1 }, domClassList);
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/src/apis/StyleSheet/__tests__/index-test.js b/src/apis/StyleSheet/__tests__/index-test.js
index e8166c39..52ca1f89 100644
--- a/src/apis/StyleSheet/__tests__/index-test.js
+++ b/src/apis/StyleSheet/__tests__/index-test.js
@@ -1,7 +1,6 @@
/* eslint-env jasmine, jest */
import StyleSheet from '..';
-import StyleRegistry from '../registry';
const isPlainObject = (x) => {
const toString = Object.prototype.toString;
@@ -15,10 +14,6 @@ const isPlainObject = (x) => {
};
describe('apis/StyleSheet', () => {
- beforeEach(() => {
- StyleRegistry.reset();
- });
-
test('absoluteFill', () => {
expect(Number.isInteger(StyleSheet.absoluteFill) === true).toBeTruthy();
});
@@ -29,7 +24,7 @@ describe('apis/StyleSheet', () => {
describe('create', () => {
test('replaces styles with numbers', () => {
- const style = StyleSheet.create({ root: { opacity: 1 } });
+ const style = StyleSheet.create({ root: { position: 'absolute' } });
expect(Number.isInteger(style.root) === true).toBeTruthy();
});
});
@@ -43,16 +38,6 @@ describe('apis/StyleSheet', () => {
});
test('renderToString', () => {
- StyleSheet.create({
- a: {
- borderWidth: 0,
- borderColor: 'red'
- },
- b: {
- position: 'absolute',
- left: 50
- }
- });
expect(StyleSheet.renderToString()).toMatchSnapshot();
});
});
diff --git a/src/apis/StyleSheet/__tests__/injector.js b/src/apis/StyleSheet/__tests__/injector.js
deleted file mode 100644
index 0e50932f..00000000
--- a/src/apis/StyleSheet/__tests__/injector.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* eslint-env jasmine, jest */
-
-import injector from '../injector';
-
-describe('apis/StyleSheet/injector', () => {
- beforeEach(() => {
- document.head.insertAdjacentHTML('afterbegin', `
-
- `);
- });
-
- test('hydrates from SSR', () => {
- const classList = injector.getClassNames();
- expect(classList).toEqual({
- 'rn-alignItems\\:stretch': true,
- 'rn-position\\:top': true
- });
- });
-});
diff --git a/src/apis/StyleSheet/__tests__/registry-test.js b/src/apis/StyleSheet/__tests__/registry-test.js
deleted file mode 100644
index f63edc61..00000000
--- a/src/apis/StyleSheet/__tests__/registry-test.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/* eslint-env jasmine, jest */
-
-import StyleRegistry from '../registry';
-
-describe('apis/StyleSheet/registry', () => {
- beforeEach(() => {
- StyleRegistry.reset();
- });
-
- test('register', () => {
- const style = { opacity: 0 };
- const id = StyleRegistry.register(style);
- expect(typeof id === 'number').toBe(true);
- });
-
- describe('resolve', () => {
- const styleA = { borderWidth: 0, borderColor: 'red', width: 100 };
- const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' };
- const styleC = { width: 200 };
-
- const testResolve = (a, b, c) => {
- // no common properties, different resolving order, same result
- const resolve1 = StyleRegistry.resolve([ a, b ]);
- expect(resolve1).toMatchSnapshot();
- const resolve2 = StyleRegistry.resolve([ b, a ]);
- expect(resolve1).toEqual(resolve2);
-
- // common properties, different resolving order, different result
- const resolve3 = StyleRegistry.resolve([ a, b, c ]);
- expect(resolve3).toMatchSnapshot();
- const resolve4 = StyleRegistry.resolve([ c, a, b ]);
- expect(resolve4).toMatchSnapshot();
- expect(resolve3).not.toEqual(resolve4);
- };
-
- test('with register, resolves to className', () => {
- const a = StyleRegistry.register(styleA);
- const b = StyleRegistry.register(styleB);
- const c = StyleRegistry.register(styleC);
- testResolve(a, b, c);
- });
-
- test('with register, resolves to mixed', () => {
- const a = styleA;
- const b = StyleRegistry.register(styleB);
- const c = StyleRegistry.register(styleC);
- testResolve(a, b, c);
- });
-
- test('without register, resolves to inline styles', () => {
- testResolve(styleA, styleB, styleC);
- });
- });
-});
diff --git a/src/apis/StyleSheet/hash.js b/src/apis/StyleSheet/hash.js
new file mode 100644
index 00000000..b1f8167d
--- /dev/null
+++ b/src/apis/StyleSheet/hash.js
@@ -0,0 +1,53 @@
+/* eslint-disable */
+/**
+ * JS Implementation of MurmurHash2
+ *
+ * @author Gary Court
+ * @see http://github.com/garycourt/murmurhash-js
+ * @author Austin Appleby
+ * @see http://sites.google.com/site/murmurhash/
+ *
+ * @param {string} str ASCII only
+ * @param {number} seed Positive integer only
+ * @return {number} 32-bit positive integer hash
+ */
+
+function murmurhash2_32_gc(str, seed) {
+ var
+ l = str.length,
+ h = seed ^ l,
+ i = 0,
+ k;
+
+ while (l >= 4) {
+ k =
+ ((str.charCodeAt(i) & 0xff)) |
+ ((str.charCodeAt(++i) & 0xff) << 8) |
+ ((str.charCodeAt(++i) & 0xff) << 16) |
+ ((str.charCodeAt(++i) & 0xff) << 24);
+
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ k ^= k >>> 24;
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
+
+ l -= 4;
+ ++i;
+ }
+
+ switch (l) {
+ case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
+ case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
+ case 1: h ^= (str.charCodeAt(i) & 0xff);
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ }
+
+ h ^= h >>> 13;
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ h ^= h >>> 15;
+
+ return h >>> 0;
+}
+
+module.exports = (str) => murmurhash2_32_gc(str, 1).toString(36);
diff --git a/src/apis/StyleSheet/hyphenate.js b/src/apis/StyleSheet/hyphenate.js
index 71fca3d4..a42222d2 100644
--- a/src/apis/StyleSheet/hyphenate.js
+++ b/src/apis/StyleSheet/hyphenate.js
@@ -1,3 +1,12 @@
-const RE_1 = /([A-Z])/g;
-const RE_2 = /^ms-/;
-module.exports = (s) => s.replace(RE_1, '-$1').toLowerCase().replace(RE_2, '-ms-');
+const cache = {};
+const MS_REGEXP = /^ms-/;
+const UPPERCASE_REGEXP = /([A-Z])/g;
+
+const hyphenateStyleProp = (prop) => {
+ if (!cache.hasOwnProperty(prop)) {
+ cache[prop] = prop.replace(UPPERCASE_REGEXP, '-$&').toLowerCase().replace(MS_REGEXP, '-ms-');
+ }
+ return cache[prop];
+};
+
+module.exports = hyphenateStyleProp;
diff --git a/src/apis/StyleSheet/index.js b/src/apis/StyleSheet/index.js
index 3e90789b..842e482b 100644
--- a/src/apis/StyleSheet/index.js
+++ b/src/apis/StyleSheet/index.js
@@ -1,14 +1,11 @@
import flattenStyle from './flattenStyle';
-import initialize from './initialize';
-import injector from './injector';
import StyleRegistry from './registry';
-initialize();
-
const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
+const absoluteFill = StyleRegistry.register(absoluteFillObject);
module.exports = {
- absoluteFill: StyleRegistry.register(absoluteFillObject),
+ absoluteFill,
absoluteFillObject,
create(styles) {
const result = {};
@@ -22,5 +19,7 @@ module.exports = {
},
hairlineWidth: 1,
flatten: flattenStyle,
- renderToString: injector.getStyleSheetHtml
+ renderToString() {
+ return StyleRegistry.getStyleSheetHtml();
+ }
};
diff --git a/src/apis/StyleSheet/initialize.js b/src/apis/StyleSheet/initialize.js
deleted file mode 100644
index 5315ac32..00000000
--- a/src/apis/StyleSheet/initialize.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import injector from './injector';
-import StyleRegistry from './registry';
-
-const initialize = () => {
- injector.addRule(
- 'reset',
- '/* React Native StyleSheet*/\n' +
- 'html{' +
- 'font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;' +
- '-webkit-tap-highlight-color:rgba(0,0,0,0)' +
- '}\n' +
- 'body{margin:0}\n' +
- 'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' +
- 'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
- 'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
- 'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}'
- );
- injector.addRule(
- 'keyframes',
- '@keyframes rn-ActivityIndicator-animation{' +
- '0%{-webkit-transform: rotate(0deg); transform: rotate(0deg);}' +
- '100%{-webkit-transform: rotate(360deg); transform: rotate(360deg);}' +
- '}\n' +
- '@keyframes rn-ProgressBar-animation{' +
- '0%{-webkit-transform: translateX(-100%); transform: translateX(-100%);}' +
- '100%{-webkit-transform: translateX(400%); transform: translateX(400%);}' +
- '}'
- );
- injector.addRule(
- 'pointer-events',
- '.rn-pointerEvents\\:auto,.rn-pointerEvents\\:box-only,.rn-pointerEvents\\:box-none *{pointer-events:auto}' +
- '.rn-pointerEvents\\:none,.rn-pointerEvents\\:box-only *,.rn-pointerEvents\\:box-none{pointer-events:none}'
- );
-
- const classNames = injector.getClassNames();
- StyleRegistry.initialize(classNames);
-};
-
-export default initialize;
diff --git a/src/apis/StyleSheet/injector.js b/src/apis/StyleSheet/injector.js
deleted file mode 100644
index 1eccd631..00000000
--- a/src/apis/StyleSheet/injector.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * Based on
- * https://github.com/lelandrichardson/react-primitives/blob/master/src/StyleSheet/injector.js
- */
-
-import asap from 'asap';
-
-const emptyObject = {};
-const hasOwnProperty = Object.prototype.hasOwnProperty;
-
-const CLASSNAME_REXEP = /\.rn-([^{;\s]+)/g;
-const STYLE_ELEMENT_ID = 'react-native-stylesheet';
-
-let registry = {};
-let isDirty = false;
-
-/**
- * Registers a rule and requests an update to the style sheet
- */
-const addRule = (key, rule) => {
- if (!registry[key]) {
- registry[key] = rule;
- isDirty = true;
- if (global.document) {
- asap(frame);
- }
- }
-};
-
-/**
- * Returns a string of the registered rules
- */
-const getStyleText = () => {
- /* eslint prefer-template:0 */
- let result = '\n';
- for (const key in registry) {
- if (hasOwnProperty.call(registry, key)) {
- result += registry[key] + '\n';
- }
- }
- return result;
-};
-
-/**
- * Returns an HTML string for server rendering
- */
-const getStyleSheetHtml = () => ``;
-
-const reset = () => { registry = {}; };
-
-/**
- * Finds or injects the style sheet when in a browser environment
- */
-let styleNode = null;
-const getStyleNode = () => {
- if (global.document) {
- if (!styleNode) {
- // look for existing style sheet (could also be server-rendered)
- styleNode = document.getElementById(STYLE_ELEMENT_ID);
- if (!styleNode) {
- // if there is no existing stylesheet, inject it style sheet
- document.head.insertAdjacentHTML('afterbegin', getStyleSheetHtml());
- styleNode = document.getElementById(STYLE_ELEMENT_ID);
- }
- }
- return styleNode;
- }
-};
-
-/**
- * Determines which classes are available in the existing document. Doesn't
- * rely on the registry so it can be used to read class names from a SSR style
- * sheet.
- */
-const getClassNames = () => {
- const styleNode = getStyleNode();
- if (styleNode) {
- const text = styleNode.textContent;
- const matches = text.match(CLASSNAME_REXEP);
- if (matches) {
- return matches.map((name) => name.slice(1)).reduce((classMap, className) => {
- classMap[className] = true;
- return classMap;
- }, {});
- }
- }
- return emptyObject;
-};
-
-const frame = () => {
- if (!isDirty || !global.document) { return; }
- isDirty = false;
-
- const styleNode = getStyleNode();
- if (styleNode) {
- const css = getStyleText();
- styleNode.textContent = css;
- }
-};
-
-module.exports = {
- addRule,
- getClassNames,
- getStyleSheetHtml,
- reset
-};
diff --git a/src/apis/StyleSheet/registry.js b/src/apis/StyleSheet/registry.js
index 193a6930..d067b76f 100644
--- a/src/apis/StyleSheet/registry.js
+++ b/src/apis/StyleSheet/registry.js
@@ -1,202 +1,3 @@
-/**
- * WARNING: changes to this file in particular can cause significant changes to
- * the results of render performance benchmarks.
- */
-
-import createReactDOMStyle from './createReactDOMStyle';
-import flattenArray from '../../modules/flattenArray';
-import flattenStyle from './flattenStyle';
-import generateCss from './generateCss';
-import I18nManager from '../I18nManager';
-import injector from './injector';
-import mapKeyValue from '../../modules/mapKeyValue';
-import prefixInlineStyles from './prefixInlineStyles';
-import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
-
-const SPACE_REGEXP = /\s/g;
-const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#*]/g;
-
-const createCacheKey = (id) => {
- const prefix = I18nManager.isRTL ? 'rtl' : 'ltr';
- return `${prefix}-${id}`;
-};
-
-/**
- * Creates an HTML class name for use on elements
- */
-const createClassName = (prop, value) => {
- const val = `${value}`.replace(SPACE_REGEXP, '-');
- return `rn-${prop}:${val}`;
-};
-
-/**
- * Formatting improves debugging in devtools and snapshot
- */
-const mapDeclarationsToClassName = (style, fn) => {
- const result = mapKeyValue(style, fn).join('\n').trim();
- return `\n${result}`;
-};
-
-/**
- * Inject a CSS rule for a given declaration and record the availability of the
- * resulting class name.
- */
-let injectedClassNames = {};
-const injectClassNameIfNeeded = (prop, value) => {
- const className = createClassName(prop, value);
- if (!injectedClassNames[className]) {
- // create a valid CSS selector for a given HTML class name
- const selector = className.replace(ESCAPE_SELECTOR_CHARS_REGEXP, '\\$&');
- const body = generateCss({ [prop]: value });
- const css = `.${selector}{${body}}`;
- // this adds the rule to the buffer to be injected into the document
- injector.addRule(className, css);
- injectedClassNames[className] = true;
- }
-
- return className;
-};
-
-/**
- * Converts a React Native style object to HTML class names
- */
-let resolvedPropsCache = {};
-const registerStyle = (id, flatStyle) => {
- const style = createReactDOMStyle(flatStyle);
- const className = mapDeclarationsToClassName(style, (prop, value) => {
- if (value != null) {
- return injectClassNameIfNeeded(prop, value);
- }
- });
-
- const key = createCacheKey(id);
- resolvedPropsCache[key] = { className };
-
- return id;
-};
-
-/**
- * Resolves a React Native style object to DOM props
- */
-const resolveProps = (reactNativeStyle) => {
- const flatStyle = flattenStyle(reactNativeStyle);
- const domStyle = createReactDOMStyle(flatStyle);
- const style = {};
-
- const className = mapDeclarationsToClassName(domStyle, (prop, value) => {
- if (value != null) {
- const singleClassName = createClassName(prop, value);
- if (injectedClassNames[singleClassName]) {
- return singleClassName;
- } else {
- // 4x slower render
- style[prop] = value;
- }
- }
- });
-
- const props = {
- className,
- style: prefixInlineStyles(style)
- };
-
- /*
- if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
- console.groupCollapsed('[StyleSheet] resolving uncached styles');
- console.log(
- 'Slow operation. Resolving style objects (uncached result). ' +
- 'Occurs on first render and when using styles not registered with "StyleSheet.create"'
- );
- console.log('source => \n', reactNativeStyle);
- console.log('flatten => \n', flatStyle);
- console.log('resolve => \n', props);
- console.groupEnd();
- }
- */
-
- return props;
-};
-
-/**
- * Caching layer over 'resolveProps'
- */
-const resolvePropsIfNeeded = (key, style) => {
- if (key) {
- if (!resolvedPropsCache[key]) {
- // slow: convert style object to props and cache
- resolvedPropsCache[key] = resolveProps(style);
- }
- return resolvedPropsCache[key];
- }
- return resolveProps(style);
-};
-
-/**
- * Web style registry
- */
-const StyleRegistry = {
- initialize(classNames) {
- injectedClassNames = classNames;
-
- /*
- if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) {
- if (global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer) {
- clearInterval(global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer);
- }
- global.__REACT_NATIVE_DEBUG_ENABLED__styleRegistryTimer = setInterval(() => {
- const entryCount = Object.keys(resolvedPropsCache).length;
- console.groupCollapsed('[StyleSheet] resolved props cache snapshot:', entryCount, 'entries');
- console.log(resolvedPropsCache);
- console.groupEnd();
- }, 30000);
- }
- */
- },
-
- reset() {
- injectedClassNames = {};
- resolvedPropsCache = {};
- injector.reset();
- },
-
- register(style) {
- const id = ReactNativePropRegistry.register(style);
- return registerStyle(id, style);
- },
-
- resolve(reactNativeStyle) {
- if (!reactNativeStyle) {
- return undefined;
- }
-
- // fast and cachable
- if (typeof reactNativeStyle === 'number') {
- const key = createCacheKey(reactNativeStyle);
- return resolvePropsIfNeeded(key, reactNativeStyle);
- }
-
- // convert a RN style object to DOM props
- if (!Array.isArray(reactNativeStyle)) {
- return resolveProps(reactNativeStyle);
- }
-
- // flatten the array
- // [ 1, [ 2, 3 ], { prop: value }, 4, 5 ] => [ 1, 2, 3, { prop: value }, 4, 5 ];
- const flatArray = flattenArray(reactNativeStyle);
-
- let isArrayOfNumbers = true;
- for (let i = 0; i < flatArray.length; i++) {
- if (typeof flatArray[i] !== 'number') {
- isArrayOfNumbers = false;
- break;
- }
- }
-
- // cache resolved props when all styles are registered
- const key = isArrayOfNumbers ? createCacheKey(flatArray.join('-')) : null;
-
- return resolvePropsIfNeeded(key, flatArray);
- }
-};
-
-module.exports = StyleRegistry;
+import StyleRegistry from './StyleRegistry';
+const registry = new StyleRegistry();
+module.exports = registry;
diff --git a/src/apis/StyleSheet/staticCss.js b/src/apis/StyleSheet/staticCss.js
new file mode 100644
index 00000000..b0f9e8b5
--- /dev/null
+++ b/src/apis/StyleSheet/staticCss.js
@@ -0,0 +1,21 @@
+module.exports =
+ // css reset
+ 'html{' +
+ 'font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;' +
+ '-webkit-tap-highlight-color:rgba(0,0,0,0);' +
+ '}\n' +
+ 'body{margin:0;}\n' +
+ 'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}\n' +
+ 'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' +
+ 'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' +
+ 'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none;}\n' +
+
+ '@keyframes rn-ActivityIndicator-animation{' +
+ '0%{-webkit-transform:rotate(0deg);transform:rotate(0deg);}' +
+ '100%{-webkit-transform:rotate(360deg);transform:rotate(360deg);}' +
+ '}\n' +
+
+ '@keyframes rn-ProgressBar-animation{' +
+ '0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)};' +
+ '100%{-webkit-transform:translateX(400%);transform:translateX(400%)};' +
+ '}';
diff --git a/src/modules/NativeMethodsMixin/index.js b/src/modules/NativeMethodsMixin/index.js
index 576fe7e6..4a6abda2 100644
--- a/src/modules/NativeMethodsMixin/index.js
+++ b/src/modules/NativeMethodsMixin/index.js
@@ -10,19 +10,6 @@ import findNodeHandle from '../findNodeHandle';
import StyleRegistry from '../../apis/StyleSheet/registry';
import UIManager from '../../apis/UIManager';
-const emptyObject = {};
-const REGEX_CLASSNAME_SPLIT = /\s+/;
-const REGEX_STYLE_PROP = /rn-(.*):.*/;
-
-const classNameFilter = (className) => { return className !== ''; };
-const classNameToList = (className = '') => className.split(REGEX_CLASSNAME_SPLIT).filter(classNameFilter);
-const getStyleProp = (className) => {
- const match = className.match(REGEX_STYLE_PROP);
- if (match) {
- return match[1];
- }
-};
-
const NativeMethodsMixin = {
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
@@ -81,41 +68,10 @@ const NativeMethodsMixin = {
setNativeProps(nativeProps: Object) {
// DOM state
const node = findNodeHandle(this);
- const domClassList = [ ...node.classList ];
+ const classList = [ ...node.classList ];
- // Resolved state
- const resolvedProps = StyleRegistry.resolve(nativeProps.style) || emptyObject;
- const resolvedClassList = classNameToList(resolvedProps.className);
-
- // Merged state
- const classList = [];
- const style = { ...resolvedProps.style };
-
- // The node has class names that we need to override.
- // Only pass on a class name when the style is unchanged.
- domClassList.forEach((c) => {
- const prop = getStyleProp(c);
- const className = resolvedProps.className;
- if (!className || className.indexOf(prop) === -1) {
- classList.push(c);
- }
- });
-
- // The node has styles that we need to override.
- // Remove any inline style that may collide with a new class name.
- resolvedClassList.forEach((c) => {
- const prop = getStyleProp(c);
- classList.push(c);
- style[prop] = null;
- });
-
- const className = `\n${classList.sort().join('\n')}`;
-
- const props = {
- ...nativeProps,
- className,
- style
- };
+ const { className, style } = StyleRegistry.resolveStateful(nativeProps.style, classList);
+ const props = { ...nativeProps, className, style };
UIManager.updateView(node, props, this);
}
diff --git a/src/modules/modality/index.js b/src/modules/modality/index.js
index 58bf4601..bc906b55 100644
--- a/src/modules/modality/index.js
+++ b/src/modules/modality/index.js
@@ -45,10 +45,13 @@ const modality = () => {
/**
* Disable the focus ring by default
*/
- const id = 'modality__';
- const style = ``;
- document.head.insertAdjacentHTML('afterbegin', style);
- const styleElement = document.getElementById(id);
+ const id = 'react-native-modality';
+ let styleElement = document.getElementById(id);
+ if (!styleElement) {
+ const style = ``;
+ document.head.insertAdjacentHTML('afterbegin', style);
+ styleElement = document.getElementById(id);
+ }
const disableFocus = () => {
if (styleElement) {