contempo/index.js
2018-12-23 07:58:10 -06:00

85 lines
1.9 KiB
JavaScript

import * as css from 'css';
import React from 'react';
let newId = 0;
const map = new WeakMap();
let sheet = null;
// Ensure styles exist for a component.
const ensureComponentStyles = (constructor, styles) => {
if (!window || !styles || map.has(constructor)) {
return;
}
newId += 1;
const id = newId.toString(36);
map.set(constructor, id);
// Ensure sheet exists.
if (!sheet) {
sheet = window.document.createElement('style');
sheet.id = 'stylist-styles';
window.document.getElementsByTagName('head')[0].appendChild(sheet);
}
// All rules made component-specific.
for (const style of styles) {
const ast = css.parse(style);
ast.stylesheet.rules = mapStyleRulesTree(ast.stylesheet.rules, id);
sheet.innerHTML += css.stringify(ast);
}
}
const mapStyleRulesTree = (rules, id) => rules.map((rule) => {
// Recur for mediaqueries.
if ('media' === rule.type) {
rule.rules = mapStyleRulesTree(rule.rules, id);
}
if (!rule.selectors) {
return rule;
}
return {
...rule,
...{
selectors: rule.selectors.map((rule) =>
`[data-component-style="${id}"] ${rule}`
),
},
};
});
export default function contempo(styles) { return function (Component) {
if (!Array.isArray(styles)) {
styles = [styles];
}
const createRenderFunction = (constructor, markupFn) => function() {
ensureComponentStyles(constructor, styles);
const markup = markupFn.apply(this, arguments);
const id = map.get(constructor);
if (!id) {
return markup;
}
return <ins data-component-style={id}>{markup}</ins>;
}
if (Component.prototype.render) {
class ContempoComponent extends Component {
}
ContempoComponent.prototype.render = createRenderFunction(
Component, Component.prototype.render
);
return ContempoComponent;
}
else {
return createRenderFunction(Component, Component);
}
}}