mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-09 12:50:53 +00:00
[change] Refactor style resolver
Minor refactor of the style resolver to convert it to a factory function.
This commit is contained in:
+19
-19
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve resolves inline-style pointerEvents to classname 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve resolves inline-style pointerEvents to classname 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-pointerEvents-12vffkv",
|
||||
@@ -9,7 +9,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register before RTL, resolves to correct className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-marginRight-zso239",
|
||||
@@ -20,7 +20,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register before RTL, resolves to correct className 2`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register before RTL, resolves to correct className 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-2s0hu9",
|
||||
@@ -31,7 +31,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to className 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderColor-4a18lf",
|
||||
@@ -45,7 +45,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 2`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to className 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderColor-4a18lf",
|
||||
@@ -59,7 +59,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to className 3`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to className 3`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-borderColor-4a18lf",
|
||||
@@ -73,7 +73,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to mixed 1`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
@@ -95,7 +95,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 2`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to mixed 2`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
@@ -117,7 +117,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve with register, resolves to mixed 3`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve with register, resolves to mixed 3`] = `
|
||||
Object {
|
||||
"classList": Array [
|
||||
"rn-left-1tsx3h3",
|
||||
@@ -139,7 +139,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve without register, resolves to inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve without register, resolves to inline styles 1`] = `
|
||||
Object {
|
||||
"classList": Array [],
|
||||
"className": "",
|
||||
@@ -160,7 +160,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve without register, resolves to inline styles 2`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve without register, resolves to inline styles 2`] = `
|
||||
Object {
|
||||
"classList": Array [],
|
||||
"className": "",
|
||||
@@ -181,7 +181,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolve without register, resolves to inline styles 3`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolve without register, resolves to inline styles 3`] = `
|
||||
Object {
|
||||
"classList": Array [],
|
||||
"className": "",
|
||||
@@ -202,7 +202,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode next class names have priority over current inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode next class names have priority over current inline styles 1`] = `
|
||||
Object {
|
||||
"className": "rn-opacity-6dt33c",
|
||||
"style": Object {
|
||||
@@ -211,7 +211,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode next inline styles have priority over current inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode next inline styles have priority over current inline styles 1`] = `
|
||||
Object {
|
||||
"className": "",
|
||||
"style": Object {
|
||||
@@ -222,14 +222,14 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode preserves unrelated class names 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode preserves unrelated class names 1`] = `
|
||||
Object {
|
||||
"className": "unknown-class-1 unknown-class-2",
|
||||
"style": Object {},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode preserves unrelated inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode preserves unrelated inline styles 1`] = `
|
||||
Object {
|
||||
"className": "",
|
||||
"style": Object {
|
||||
@@ -239,7 +239,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles 1`] = `
|
||||
Object {
|
||||
"className": "",
|
||||
"style": Object {
|
||||
@@ -250,7 +250,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true, resolves to flipped classNames 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true, resolves to flipped classNames 1`] = `
|
||||
Object {
|
||||
"className": "rn-left-1u10d71 rn-marginRight-zso239",
|
||||
"style": Object {
|
||||
@@ -260,7 +260,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`StyleSheet/ReactNativeStyleResolver resolveWithNode when isRTL=true, resolves to flipped inline styles 1`] = `
|
||||
exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true, resolves to flipped inline styles 1`] = `
|
||||
Object {
|
||||
"className": "",
|
||||
"style": Object {
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
import I18nManager from '../../I18nManager';
|
||||
import ReactNativePropRegistry from '../../../modules/ReactNativePropRegistry';
|
||||
import ReactNativeStyleResolver from '../ReactNativeStyleResolver';
|
||||
import createStyleResolver from '../createStyleResolver';
|
||||
|
||||
let styleResolver;
|
||||
|
||||
describe('StyleSheet/ReactNativeStyleResolver', () => {
|
||||
describe('StyleSheet/createStyleResolver', () => {
|
||||
beforeEach(() => {
|
||||
styleResolver = new ReactNativeStyleResolver();
|
||||
styleResolver = createStyleResolver({ insert() {} });
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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, inline, stringifyValueWithProperty } from './compile';
|
||||
import initialRules from './initialRules';
|
||||
import modality from './modality';
|
||||
import { STYLE_ELEMENT_ID, STYLE_GROUPS } from './constants';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
export default function createStyleResolver() {
|
||||
let resolved, inserted, sheet, lookup;
|
||||
|
||||
const init = () => {
|
||||
resolved = { ltr: {}, rtl: {}, rtlNoSwap: {} };
|
||||
inserted = { ltr: {}, rtl: {}, rtlNoSwap: {} };
|
||||
sheet = createOrderedCSSStyleSheet(createCSSStyleSheet(STYLE_ELEMENT_ID));
|
||||
lookup = {
|
||||
byClassName: {},
|
||||
byProp: {}
|
||||
};
|
||||
modality(rule => sheet.insert(rule, STYLE_GROUPS.modality));
|
||||
initialRules.forEach(rule => {
|
||||
sheet.insert(rule, STYLE_GROUPS.reset);
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function addToLookup(className, prop, value) {
|
||||
if (!lookup.byProp[prop]) {
|
||||
lookup.byProp[prop] = {};
|
||||
}
|
||||
lookup.byProp[prop][value] = className;
|
||||
lookup.byClassName[className] = { prop, value };
|
||||
}
|
||||
|
||||
function getClassName(prop, value) {
|
||||
const val = stringifyValueWithProperty(value, prop);
|
||||
const cache = lookup.byProp;
|
||||
return cache[prop] && cache[prop].hasOwnProperty(val) && cache[prop][val];
|
||||
}
|
||||
|
||||
function _injectRegisteredStyle(id) {
|
||||
const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
|
||||
const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
|
||||
if (!inserted[dir][id]) {
|
||||
const style = createCompileableStyle(i18nStyle(flattenStyle(id)));
|
||||
const results = atomic(style);
|
||||
Object.values(results).forEach(({ identifier, property, rules, value }) => {
|
||||
addToLookup(identifier, property, value);
|
||||
rules.forEach(rule => {
|
||||
const 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) {
|
||||
if (!style) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
// fast and cachable
|
||||
if (typeof style === 'number') {
|
||||
_injectRegisteredStyle(style);
|
||||
const key = createCacheKey(style);
|
||||
return _resolveStyle(style, key);
|
||||
}
|
||||
|
||||
// resolve a plain RN style object
|
||||
if (!Array.isArray(style)) {
|
||||
return _resolveStyle(style);
|
||||
}
|
||||
|
||||
// flatten the style array
|
||||
// cache resolved props when all styles are registered
|
||||
// otherwise fallback to resolving
|
||||
const flatArray = flattenArray(style);
|
||||
let isArrayOfNumbers = true;
|
||||
let cacheKey = '';
|
||||
for (let i = 0; i < flatArray.length; i++) {
|
||||
const id = flatArray[i];
|
||||
if (typeof id !== 'number') {
|
||||
isArrayOfNumbers = false;
|
||||
} else {
|
||||
if (isArrayOfNumbers) {
|
||||
cacheKey += id + '-';
|
||||
}
|
||||
_injectRegisteredStyle(id);
|
||||
}
|
||||
}
|
||||
const key = isArrayOfNumbers ? createCacheKey(cacheKey) : null;
|
||||
return _resolveStyle(flatArray, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a React Native style object to DOM attributes, accounting for
|
||||
* the existing styles applied to the DOM node.
|
||||
*
|
||||
* To determine the next style, some of the existing DOM state must be
|
||||
* converted back into React Native styles.
|
||||
*/
|
||||
function resolveWithNode(rnStyleNext, node) {
|
||||
function getDeclaration(className) {
|
||||
return lookup.byClassName[className] || emptyObject;
|
||||
}
|
||||
|
||||
const { classList: rdomClassList, style: rdomStyle } = getDOMStyleInfo(node);
|
||||
// Convert the DOM classList back into a React Native form
|
||||
// Preserves unrecognized class names.
|
||||
const { classList: rnClassList, style: rnStyle } = rdomClassList.reduce(
|
||||
(styleProps, className) => {
|
||||
const { prop, value } = getDeclaration(className);
|
||||
if (prop) {
|
||||
styleProps.style[prop] = value;
|
||||
} else {
|
||||
styleProps.classList.push(className);
|
||||
}
|
||||
return styleProps;
|
||||
},
|
||||
{ classList: [], style: {} }
|
||||
);
|
||||
|
||||
// Create next DOM style props from current and next RN styles
|
||||
const { classList: rdomClassListNext, style: rdomStyleNext } = resolve([
|
||||
i18nStyle(rnStyle),
|
||||
rnStyleNext
|
||||
]);
|
||||
|
||||
// Final className
|
||||
// Add the current class names not managed by React Native
|
||||
const className = classListToString(rdomClassListNext.concat(rnClassList));
|
||||
|
||||
// Final style
|
||||
// Next class names take priority over current inline styles
|
||||
const style = { ...rdomStyle };
|
||||
rdomClassListNext.forEach(className => {
|
||||
const { prop } = getDeclaration(className);
|
||||
if (style[prop]) {
|
||||
style[prop] = '';
|
||||
}
|
||||
});
|
||||
// Next inline styles take priority over current inline styles
|
||||
Object.assign(style, rdomStyleNext);
|
||||
|
||||
return { className, style };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a React Native style object
|
||||
*/
|
||||
function _resolveStyle(style, key) {
|
||||
const { doLeftAndRightSwapInRTL, isRTL } = I18nManager;
|
||||
const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
|
||||
|
||||
// faster: memoized
|
||||
if (key != null && resolved[dir][key] != null) {
|
||||
return resolved[dir][key];
|
||||
}
|
||||
|
||||
const flatStyle = flattenStyle(style);
|
||||
const localizedStyle = createCompileableStyle(i18nStyle(flatStyle));
|
||||
|
||||
// slower: convert style object to props and cache
|
||||
const props = Object.keys(localizedStyle)
|
||||
.sort()
|
||||
.reduce(
|
||||
(props, styleProp) => {
|
||||
const value = localizedStyle[styleProp];
|
||||
if (value != null) {
|
||||
const 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 === 'pointerEvents' ||
|
||||
styleProp === 'placeholderTextColor' ||
|
||||
styleProp === 'animationKeyframes'
|
||||
) {
|
||||
const a = atomic({ [styleProp]: value });
|
||||
Object.values(a).forEach(({ identifier, rules }) => {
|
||||
props.classList.push(identifier);
|
||||
rules.forEach(rule => {
|
||||
sheet.insert(rule, STYLE_GROUPS.atomic);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (!props.style) {
|
||||
props.style = {};
|
||||
}
|
||||
// 4x slower render
|
||||
props.style[styleProp] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return props;
|
||||
},
|
||||
{ classList: [] }
|
||||
);
|
||||
|
||||
props.className = classListToString(props.classList);
|
||||
if (props.style) {
|
||||
props.style = inline(props.style);
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
resolved[dir][key] = props;
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
return {
|
||||
getStyleSheet() {
|
||||
const textContent = sheet.getTextContent();
|
||||
// Reset state on the server so critical css is always the result
|
||||
if (!canUseDOM) {
|
||||
init();
|
||||
}
|
||||
|
||||
return {
|
||||
id: STYLE_ELEMENT_ID,
|
||||
textContent
|
||||
};
|
||||
},
|
||||
resolve,
|
||||
sheet,
|
||||
resolveWithNode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Misc helpers
|
||||
*/
|
||||
const createCacheKey = id => {
|
||||
const prefix = 'rn';
|
||||
return `${prefix}-${id}`;
|
||||
};
|
||||
|
||||
const classListToString = list => list.join(' ').trim();
|
||||
|
||||
/**
|
||||
* Copies classList and style data from a DOM node
|
||||
*/
|
||||
const hyphenPattern = /-([a-z])/g;
|
||||
const toCamelCase = str => str.replace(hyphenPattern, m => m[1].toUpperCase());
|
||||
|
||||
const getDOMStyleInfo = node => {
|
||||
const nodeStyle = node.style;
|
||||
const classList = Array.prototype.slice.call(node.classList);
|
||||
const style = {};
|
||||
// DOM style is a CSSStyleDeclaration
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
|
||||
for (let i = 0; i < nodeStyle.length; i += 1) {
|
||||
const property = nodeStyle.item(i);
|
||||
if (property) {
|
||||
// DOM style uses hyphenated prop names and may include vendor prefixes
|
||||
// Transform back into React DOM style.
|
||||
style[toCamelCase(property)] = nodeStyle.getPropertyValue(property);
|
||||
}
|
||||
}
|
||||
return { classList, style };
|
||||
};
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ReactNativeStyleResolver from './ReactNativeStyleResolver';
|
||||
const styleResolver = new ReactNativeStyleResolver();
|
||||
import createStyleResolver from './createStyleResolver';
|
||||
const styleResolver = createStyleResolver();
|
||||
export default styleResolver;
|
||||
|
||||
Reference in New Issue
Block a user