All files / vanilla-dom/src create-component.js

89.74% Statements 35/39
66.67% Branches 4/6
78.95% Functions 15/19
89.19% Lines 33/37
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          4x                 5x 5x 5x       5x     5x             5x 5x                   5x 5x 5x 3x 3x 3x           3x 3x 3x 3x                 8x 8x 8x 6x 6x           4x         3x 3x       2x 3x                 5x 5x 5x           8x       1x  
import { format } from '../../core/template';
import matchAttribute from '../../core/match-attribute';
import bindAttrsToCSSOM from '../../dom/dist/bind-attrs-to-cssom';
import generateClassName from '../../dom/dist/generate-class-name';
 
const getAttributeClassNames = attributes => props =>
  attributes
    .filter(attribute => matchAttribute(attribute, props[attribute.name]))
    .map(attribute => attribute.className);
 
class CSSComponent {
  willUpdate = false;
 
  constructor(element, attrs, props = {}) {
    this.element = element;
    this.attrs = attrs;
    this.props = props;
  }
 
  observe(properties) {
    Object.defineProperties(
      this.element,
      properties.reduce(
        (acc, property) => ({
          ...acc,
          [property]: {
            get: () => {
              return this.props[property];
            },
            set: value => {
              this.update({ ...this.props, [property]: value });
              return value;
            },
          },
        }),
        {}
      )
    );
  }
 
  update(nextProps) {
    const prevProps = this.props;
    this.props = nextProps;
    if (!this.willUpdate) {
      this.willUpdate = true;
      setTimeout(() => {
        this.element.dispatchEvent(
          Object.assign(new Event('willUpdateStyle'), {
            props: prevProps,
            nextProps: this.props,
          })
        );
        this.willUpdate = false;
        requestAnimationFrame(() => {
          this.render();
          this.element.dispatchEvent(
            Object.assign(new Event('didUpdateStyle'), { prevProps, props: this.props })
          );
        });
      });
    }
  }
 
  render() {
    const { props } = this;
    this.element.className = this.className;
    for (const attr of this.attrs) {
      Eif (attr.cssRule) {
        attr.cssRule.style[attr.prop] = format(attr.template, props);
      }
    }
  }
}
 
const createComponent = ({ className, attributes, attrs, base = 'div' }) => class
  extends CSSComponent {
    static getAttributeClassNames = getAttributeClassNames(attributes);
 
    static create(initialAttributes) {
      const instance = new this(initialAttributes);
      return instance.element;
    }
 
    static propKeys = [
      ...attributes.map(attribute => attribute.name),
      ...attrs.reduce((acc, attr) => acc.concat(attr.attributes), []),
    ];
 
    static className = className;
    static attributes = attributes;
    static attrs = attrs;
    static base = base;
 
    constructor(props) {
      super(document.createElement(base), bindAttrsToCSSOM(attrs), props);
      this.observe(this.constructor.propKeys);
      this.render();
    }
 
    generateClassName = generateClassName({ className, attributes, attrs: this.attrs });
 
    get className() {
      return this.generateClassName(this.props);
    }
};
 
module.exports = createComponent;