/** * 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. * * */ /** * WARNING: changes to this file in particular can cause significant changes to * the results of render performance benchmarks. */ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import createCSSStyleSheet from './createCSSStyleSheet'; import createCompileableStyle from './createCompileableStyle'; import createOrderedCSSStyleSheet from './createOrderedCSSStyleSheet'; import flattenArray from '../../modules/flattenArray'; import flattenStyle from './flattenStyle'; import I18nManager from '../I18nManager'; import i18nStyle from './i18nStyle'; import { atomic, classic, inline, stringifyValueWithProperty } from './compile'; import initialRules from './initialRules'; import modality from './modality'; import { STYLE_ELEMENT_ID, STYLE_GROUPS } from './constants'; export default function createStyleResolver() { var inserted, sheet, cache; var resolved = { css: {}, ltr: {}, rtl: {}, rtlNoSwap: {} }; var init = function init() { inserted = { css: {}, ltr: {}, rtl: {}, rtlNoSwap: {} }; sheet = createOrderedCSSStyleSheet(createCSSStyleSheet(STYLE_ELEMENT_ID)); cache = {}; modality(function (rule) { return sheet.insert(rule, STYLE_GROUPS.modality); }); initialRules.forEach(function (rule) { sheet.insert(rule, STYLE_GROUPS.reset); }); }; init(); function addToCache(className, prop, value) { if (!cache[prop]) { cache[prop] = {}; } cache[prop][value] = className; } function getClassName(prop, value) { var val = stringifyValueWithProperty(value, prop); return cache[prop] && cache[prop].hasOwnProperty(val) && cache[prop][val]; } function _injectRegisteredStyle(id) { var doLeftAndRightSwapInRTL = I18nManager.doLeftAndRightSwapInRTL, isRTL = I18nManager.isRTL; var dir = isRTL ? doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap' : 'ltr'; if (!inserted[dir][id]) { var style = createCompileableStyle(i18nStyle(flattenStyle(id))); var results = atomic(style); Object.keys(results).forEach(function (key) { var _results$key = results[key], identifier = _results$key.identifier, property = _results$key.property, rules = _results$key.rules, value = _results$key.value; addToCache(identifier, property, value); rules.forEach(function (rule) { var group = STYLE_GROUPS.custom[property] || STYLE_GROUPS.atomic; sheet.insert(rule, group); }); }); inserted[dir][id] = true; } } /** * Resolves a React Native style object to DOM attributes */ function resolve(style, classList) { var nextClassList = []; var props = {}; if (!style && !classList) { return props; } if (Array.isArray(classList)) { flattenArray(classList).forEach(function (identifier) { if (identifier) { if (inserted.css[identifier] == null && resolved.css[identifier] != null) { var item = resolved.css[identifier]; item.rules.forEach(function (rule) { sheet.insert(rule, item.group); }); inserted.css[identifier] = true; } nextClassList.push(identifier); } }); } if (typeof style === 'number') { // fast and cachable _injectRegisteredStyle(style); var key = createCacheKey(style); props = _resolveStyle(style, key); } else if (!Array.isArray(style)) { // resolve a plain RN style object props = _resolveStyle(style); } else { // flatten the style array // cache resolved props when all styles are registered // otherwise fallback to resolving var flatArray = flattenArray(style); var isArrayOfNumbers = true; var cacheKey = ''; for (var i = 0; i < flatArray.length; i++) { var id = flatArray[i]; if (typeof id !== 'number') { isArrayOfNumbers = false; } else { if (isArrayOfNumbers) { cacheKey += id + '-'; } _injectRegisteredStyle(id); } } var _key = isArrayOfNumbers ? createCacheKey(cacheKey) : null; props = _resolveStyle(flatArray, _key); } nextClassList.push.apply(nextClassList, props.classList); var finalProps = { className: classListToString(nextClassList), classList: nextClassList }; if (props.style) { finalProps.style = props.style; } return finalProps; } /** * Resolves a React Native style object */ function _resolveStyle(style, key) { var doLeftAndRightSwapInRTL = I18nManager.doLeftAndRightSwapInRTL, isRTL = I18nManager.isRTL; var dir = isRTL ? doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap' : 'ltr'; // faster: memoized if (key != null && resolved[dir][key] != null) { return resolved[dir][key]; } var flatStyle = flattenStyle(style); var localizedStyle = createCompileableStyle(i18nStyle(flatStyle)); // slower: convert style object to props and cache var props = Object.keys(localizedStyle).sort().reduce(function (props, styleProp) { var value = localizedStyle[styleProp]; if (value != null) { var className = getClassName(styleProp, value); if (className) { props.classList.push(className); } else { // Certain properties and values are not transformed by 'createReactDOMStyle' as they // require more complex transforms into multiple CSS rules. Here we assume that StyleManager // can bind these styles to a className, and prevent them becoming invalid inline-styles. if (styleProp === 'animationKeyframes' || styleProp === 'placeholderTextColor' || styleProp === 'pointerEvents' || styleProp === 'scrollbarWidth') { var _atomic; var a = atomic((_atomic = {}, _atomic[styleProp] = value, _atomic)); Object.keys(a).forEach(function (key) { var _a$key = a[key], identifier = _a$key.identifier, rules = _a$key.rules; props.classList.push(identifier); rules.forEach(function (rule) { sheet.insert(rule, STYLE_GROUPS.atomic); }); }); } else { if (!props.style) { props.style = {}; } // 4x slower render props.style[styleProp] = value; } } } return props; }, { classList: [] }); if (props.style) { props.style = inline(props.style); } if (key != null) { resolved[dir][key] = props; } return props; } return { getStyleSheet: function getStyleSheet() { var textContent = sheet.getTextContent(); // Reset state on the server so critical css is always the result if (!canUseDOM) { init(); } return { id: STYLE_ELEMENT_ID, textContent: textContent }; }, createCSS: function createCSS(rules, group) { var result = {}; Object.keys(rules).forEach(function (name) { var style = rules[name]; var compiled = classic(style, name); Object.keys(compiled).forEach(function (key) { var _compiled$key = compiled[key], identifier = _compiled$key.identifier, rules = _compiled$key.rules; resolved.css[identifier] = { group: group || STYLE_GROUPS.classic, rules: rules }; result[name] = identifier; }); }); return result; }, resolve: resolve, sheet: sheet }; } /** * Misc helpers */ var createCacheKey = function createCacheKey(id) { var prefix = 'rn'; return prefix + "-" + id; }; var classListToString = function classListToString(list) { return list.join(' ').trim(); };