Trip-Trax/tt-stylesheet

View on GitHub
src/tt-stylesheet.js

Summary

Maintainability
C
1 day
Test Coverage
const TYPE_ARRAY = '[object Array]';
const TYPE_STRING = '[object String]';
const TYPE_OBJECT = '[object Object]';
const TYPE_BOOLEAN = '[object Boolean]';
const TYPE_NUMBER = '[object Number]';
const PREFIXES = '-webkit-,-ms-,-moz-,-o-'.split(',');
const PREFIXES_LEN = PREFIXES.length;
const COMPUTED_STYLES = window.getComputedStyle(document.documentElement);
const NON_UNIT_PROPERTIES = {
  'animation-iteration-count': true,
  'box-ordinal-group': true,
  'column-count': true,
  'fill-opacity': true,
  'flex': true,
  'flex-grow': true,
  'flex-order': true,
  'flex-shrink': true,
  'font-weight': true,
  'line-height': true,
  'opacity': true,
  'order': true,
  'orphans': true,
  'stop-opacity': true,
  'tab-size': 1,
  'widows': true,
  'z-index': true,
  'zoom': true
};

export default class {
  constructor (target) {
    this._styleSheetEnabled = false;
    this._stylesheetElement = null;
    this._stylesheet = this._initializeStyleElement(target);
    this._CACHED_STYLES = {};
    this._CACHED_PROPERTIES = {};
    this._BROWSER_STYLES = [].slice.call(COMPUTED_STYLES);
  }

  _getType (type) {
    return Object.prototype.toString.call(type);
  }

  _isNumber (value) {
    return !!(this._getType(value) === TYPE_NUMBER);
  }

  _isString (value) {
    return !!(this._getType(value) === TYPE_STRING);
  }

  _isObject (value) {
    return !!(this._getType(value) === TYPE_OBJECT);
  }

  _isArray (value) {
    return !!(this._getType(value) === TYPE_ARRAY);
  }

  _isBoolean (value) {
    return !!(this._getType(value) === TYPE_BOOLEAN);
  }

  _isElement (node) {
    return !!(node && (node.nodeName || (node.prop && node.attr && node.find)));
  }

  _initializeStyleElement (target) {
    var style = document.createElement('style');

    style.setAttribute('type', 'text/css');
    style.setAttribute('data-ttstylesheet', 'true');
    style.appendChild(document.createTextNode(''));

    ((!target && !this._isElement(target)) ? document.head : target).appendChild(style);

    this._styleSheetEnabled = true;
    this._stylesheetElement = style;

    return (style.styleSheet || style.sheet);
  }

  _dasherize(property) {
    return property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  _normalizeProperty (property) {
    if (this._CACHED_PROPERTIES.hasOwnProperty(property)) {
      return this._CACHED_PROPERTIES[property];
    }

    var dasherizedProperty = this._dasherize(property);

    if (this._BROWSER_STYLES.indexOf(dasherizedProperty) !== -1) {
      this._CACHED_PROPERTIES[property] = dasherizedProperty;
      return dasherizedProperty;
    } else {
      let i = PREFIXES_LEN - 1,
        prefixed = null;

      while (i >= 0) {
        prefixed = `${PREFIXES[i]}${dasherizedProperty}`;

        if (this._BROWSER_STYLES.indexOf(prefixed) !== -1) {
          this._CACHED_PROPERTIES[property] = prefixed;
          return prefixed;
        }

        i--;
      }
    }

    this._CACHED_PROPERTIES[property] = dasherizedProperty;

    return dasherizedProperty;
  }

  _insertRule (selector, styles, index) {
    const sheet = this._stylesheet;

    styles = this._parseStyles(styles);

    if (sheet.cssRules.length !== 0 && sheet.cssRules[index]) {
      sheet.deleteRule(index );
    }

    sheet.insertRule(`${selector} { ${styles} }`, index);
  }

  _isValidForUnit (property) {
    if (NON_UNIT_PROPERTIES.hasOwnProperty(property)) {
      return false;
    }

    return true;
  }

  _parseStyles (styles) {
    const stylesToText = Object.keys(styles).map((key) => {
      const property = this._normalizeProperty(key);
      const value = styles[key];
      const validForUnit = this._isValidForUnit(property) && this._isNumber(value);
      const parsedValue = validForUnit ? `${value}px` : value;
      const declaration = `${property}: ${parsedValue} !important`;

      return declaration;
    }).join(';') + ';';

    return stylesToText;
  }

  _getKey (key, replacer) {
    if (key.indexOf('&') !== -1) {
      key = key.replace('&', replacer);
    } else {
      key = replacer + ' ' + key;
    }

    return key;
  }

  _flattenToOneLevel( data) {
    for (let key in data) {
      const styles = data[key];

      for (let _key in styles) {
        if (this._isObject(styles[_key])) {
          const oldKey = _key;

          _key = this._getKey(_key, key);
          data[_key] = styles[oldKey];

          delete data[key][oldKey];
        }
      }
    }

    return data;
  }

  _insertRules (selector, objectStyles) {
    let cachedStyle = null;

    if (this._CACHED_STYLES.hasOwnProperty(selector)) {
      cachedStyle = this._CACHED_STYLES[selector];

      const { obj } = cachedStyle;

      this._CACHED_STYLES[selector] = Object.assign(cachedStyle, {
        obj: Object.assign(obj, objectStyles)
      });
    } else {
      this._CACHED_STYLES[selector] = {
        slctor: selector,
        obj: objectStyles,
        index : Object.keys(this._CACHED_STYLES).length
      };

      cachedStyle = this._CACHED_STYLES[selector];
    }

    const { slctor, obj, index } = cachedStyle;

    this._insertRule(slctor, obj, index);
  }

  _insertJSONRules (rules) {
    rules = this._flattenToOneLevel(rules);

    for (let rule in rules) {
      if (rules.hasOwnProperty(rule)) {
        const selector = rule;
        const selectorRules = rules[rule];
        this._insertRules(selector, selectorRules);
      }
    }
  }

  _disableStylesheet () {
    if (this._styleSheetEnabled) {
      const sheet = this._stylesheet;

      sheet.disabled = true;
      this._styleSheetEnabled = false;
    }
  }

  _enableStylesheet () {
    if (!this._styleSheetEnabled) {
      const sheet = this._stylesheet;

      sheet.disabled = false;
      this._styleSheetEnabled = true;
    }
  }

  add (styles) {
    if (styles && this._isObject(styles)) {
      this._insertJSONRules(styles);
    }

    return this;
  }

  disable () {
    this._disableStylesheet();
    return this;
  }

  enable () {
    this._enableStylesheet();
    return this;
  }

  initCSS () {
    const cssRules = this._stylesheet.cssRules;
    const styleElem = this._stylesheetElement;

    if (this._isArray(cssRules)) {
      const len = cssRules.length;

      if (len === 0) {
        return this;
      }

      styleElem.innerHTML = '';

      for (let i = 0; i < len; i++) {
        if (cssRules[i] && cssRules[i].cssText) {
          styleElem.appendChild(document.createTextNode(`${cssRules[i].cssText}`));
        }
      }
    } else {
      for (let key in cssRules) {
        if (cssRules.hasOwnProperty(key)) {
          if (cssRules[key] && cssRules[key].cssText) {
            styleElem.appendChild(document.createTextNode(`${cssRules[key].cssText}`));
          }
        }
      }
    }
  }
}