All files / react-native-web/src/exports/StyleSheet createStyleResolver.js

99.11% Statements 111/112
95.38% Branches 62/65
100% Functions 23/23
99.08% Lines 108/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255                                                          25x   25x 31x 31x 31x 31x 31x 124x       25x     204x 154x   204x       1558x 1558x       890x 890x 890x 76x 76x 76x 204x 204x 204x 216x 216x     76x               1045x 1045x   1045x       1045x 1030x 690x 690x 36x 36x 36x   36x     690x         1045x   273x 273x 273x 772x   346x         426x 426x 426x 426x 848x 848x 231x   617x 611x   617x     426x 426x     1045x   1045x       1045x 108x     1045x             1045x 1045x     1045x 473x     572x 572x     572x       1698x 1698x 1558x 1558x 1365x         193x           1x 1x 1x 1x 1x 2x       192x 108x     192x       1698x         572x 108x     572x 39x     572x     25x   6x   6x 6x     6x           42x 42x 76x 76x   76x 76x 76x 76x     42x       2x               20x 512x 512x     1045x  
/**
 * 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, 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() {
  let inserted, sheet, cache;
  const resolved = { css: {}, ltr: {}, rtl: {}, rtlNoSwap: {} };
 
  const init = () => {
    inserted = { css: {}, ltr: {}, rtl: {}, rtlNoSwap: {} };
    sheet = createOrderedCSSStyleSheet(createCSSStyleSheet(STYLE_ELEMENT_ID));
    cache = {};
    modality((rule) => sheet.insert(rule, STYLE_GROUPS.modality));
    initialRules.forEach((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) {
    const val = stringifyValueWithProperty(value, prop);
    return cache[prop] && cache[prop].hasOwnProperty(val) && cache[prop][val];
  }
 
  function _injectRegisteredStyle(id) {
    const { doLeftAndRightSwapInRTL, isRTL } = I18nManager.getConstants();
    const dir = isRTL ? (doLeftAndRightSwapInRTL ? 'rtl' : 'rtlNoSwap') : 'ltr';
    if (!inserted[dir][id]) {
      const style = createCompileableStyle(i18nStyle(flattenStyle(id)));
      const results = atomic(style);
      Object.keys(results).forEach((key) => {
        const { identifier, property, rules, value } = results[key];
        addToCache(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, classList) {
    const nextClassList = [];
    let props = {};
 
    Iif (!style && !classList) {
      return props;
    }
 
    if (Array.isArray(classList)) {
      flattenArray(classList).forEach((identifier) => {
        Eif (identifier) {
          if (inserted.css[identifier] == null && resolved.css[identifier] != null) {
            const item = resolved.css[identifier];
            item.rules.forEach((rule) => {
              sheet.insert(rule, item.group);
            });
            inserted.css[identifier] = true;
          }
 
          nextClassList.push(identifier);
        }
      });
    }
 
    if (typeof style === 'number') {
      // fast and cachable
      _injectRegisteredStyle(style);
      const 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
      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;
      props = _resolveStyle(flatArray, key);
    }
 
    nextClassList.push(...props.classList);
 
    const 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) {
    const { doLeftAndRightSwapInRTL, isRTL } = I18nManager.getConstants();
    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 === 'animationKeyframes' ||
                styleProp === 'placeholderTextColor' ||
                styleProp === 'pointerEvents' ||
                styleProp === 'scrollbarWidth'
              ) {
                const a = atomic({ [styleProp]: value });
                Object.keys(a).forEach((key) => {
                  const { identifier, rules } = a[key];
                  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: [] }
      );
 
    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
      Eif (!canUseDOM) {
        init();
      }
 
      return {
        id: STYLE_ELEMENT_ID,
        textContent
      };
    },
    createCSS(rules, group) {
      const result = {};
      Object.keys(rules).forEach((name) => {
        const style = rules[name];
        const compiled = classic(style, name);
 
        Object.keys(compiled).forEach((key) => {
          const { identifier, rules } = compiled[key];
          resolved.css[identifier] = { group: group || STYLE_GROUPS.classic, rules };
          result[name] = identifier;
        });
      });
      return result;
    },
    resolve,
    get sheet() {
      return sheet;
    }
  };
}
 
/**
 * Misc helpers
 */
const createCacheKey = (id) => {
  const prefix = 'rn';
  return `${prefix}-${id}`;
};
 
const classListToString = (list) => list.join(' ').trim();