282 lines
7.9 KiB
JavaScript
282 lines
7.9 KiB
JavaScript
/**
|
|
* 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();
|
|
}; |