tim-evans/dinosheets

View on GitHub
lib/shim.js

Summary

Maintainability
C
7 hrs
Test Coverage
import { camelize, dasherize, toCSS, forEach, keys, reduce, map, trim } from 'dinosheets/utils';
import supports from 'dinosheets/supports';

class StyleSheetShim {
  constructor(styleSheet) {
    this.styleSheet = styleSheet;
    this.indexes = {};
    this.styles = {};
  }

  rules() {
    const { styleSheet } = this;
    return styleSheet.cssRules || styleSheet.rules || [];
  }

  ruleFor(selector) {
    const index = this.indexes[selector];
    if (index == null) { return null; }
    const rules = this.rules();

    return {
      index,
      rule: rules[index]
    };
  }

  insertRule(selector, rules, index) {
    const { styleSheet } = this;

    // Lint out all rules that can't be applied
    rules = reduce(keys(rules), (E, rule) => {
      let value = rules[rule];
      if (supports(rule, value)) {
        E[rule] = value;
      }
      return E;
    }, {});

    const css = toCSS(rules);

    if (index == null) {
      index = this.rules().length;
    }

    if (css.length) {
      this.indexes[selector] = index;
      this.styles[selector] = rules;
      if (styleSheet.insertRule) {
        styleSheet.insertRule(`${selector} { ${css} }`, index);
      } else {
        styleSheet.addRule(selector, css, index);

        if (this.ruleFor(selector).rule.selectorText.toLowerCase() !== selector.toLowerCase()) {
          delete this.indexes[selector];
          delete this.styles[selector];
          this._splitsSelectors = true;
          forEach(map(selector.split(','), trim), (selector) => {
            this.indexes[selector] = index++;
            this.styles[selector] = rules;
          });
        }
      }
    }
  }

  updateRule(selector, rules) {
    if (this._splitsSelectors && selector.indexOf(',') !== -1) {
      forEach(map(selector.split(','), trim), (selector) => {
        this.updateRule(selector, rules);
      });
      return;
    }

    // Lint out all rules that can't be applied
    rules = reduce(keys(rules), (E, rule) => {
      let value = rules[rule];
      if (supports(rule, value)) {
        E[rule] = value;
      }
      return E;
    }, {});

    const { rule } = this.ruleFor(selector);
    for (let key in rules) {
      if (!rules.hasOwnProperty(key)) { continue; }
      let value = rules[key];
      key = camelize(key);
      if (key == null) { continue; }

      if (value == null) {
        delete this.styles[selector][key];
        if (rule.style.removeProperty) {
          rule.style.removeProperty(dasherize(key));
        } else {
          rule.style[key] = null;
        }
      } else {
        this.styles[selector][key] = value;
        rule.style[key] = value;
      }
    }
  }

  insertOrUpdateRule(selector, rules) {
    if (this.ruleFor(selector)) {
      this.updateRule(selector, rules);
    } else {
      this.insertRule(selector, rules);
    }
  }

  deleteRule(selector) {
    if (this._splitsSelectors && selector.indexOf(',') !== -1) {
      forEach(map(selector.split(','), trim), (selector) => {
        this.deleteRule(selector);
      });
      return;
    }

    const { styleSheet } = this;
    const { index } = this.ruleFor(selector);
    if (styleSheet.deleteRule) {
      styleSheet.deleteRule(selector, index);
    } else {
      styleSheet.removeRule(index);
    }
    delete this.indexes[selector];
    delete this.styles[selector];
  }

  toString() {
    let sortedStyles = [];
    let styles = this.styles;
    let indexes = this.indexes;

    for (let selector in styles) {
      let rules = styles[selector];
      let index = indexes[selector];
      sortedStyles[index] = [selector, rules];
    }

    let css = [];
    for (let i = 0, len = sortedStyles.length; i < len; i++) {
      let style = sortedStyles[i];
      if (style == null) { continue; }
      let selector = style[0];
      let rules = style[1];
      css.push([
        `${selector} {\n`,
        toCSS(rules, { tab: '  ', newline: '\n' }),
        '}'
      ].join(''));
    }
    return css.join('\n\n');
  }
}

export default StyleSheetShim;