Skip to content

Commit

Permalink
feat: 🎸 add vcssom addon
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Mar 21, 2019
1 parent 6f1ead5 commit 63e27f2
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
51 changes: 51 additions & 0 deletions .storybook/VCSSOM.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {createElement as h} from 'react';
import {storiesOf} from '@storybook/react';
const {create} = require('../index');
const {addon: addonCSSOM} = require('../addon/cssom');
const {addon: addonVCSSOM} = require('../addon/vcssom');

const nano = create();
addonCSSOM(nano);
addonVCSSOM(nano);

const sheet = new nano.VSheet();
sheet.diff({
'': {
'.test_vcssom': {
color: 'green',
}
}
});
sheet.diff({
'': {
'.test_vcssom': {
border: '1px solid tomato',
}
},
'@media only screen and (max-width: 600px)': {
'.test_vcssom': {
'text-decoration': 'underline',
},
}
});
sheet.diff({
'': {
'.test_vcssom': {
color: 'orange',
'text-transform': 'uppercase',
'font-style': 'italic',
'text-align': 'center',
border: '1px solid black',
},
'.test_vcssom:hover': {
color: 'red',
}
}
});

console.log('sheet', sheet);

storiesOf('Addons/VCSSOM', module)
.add('rule', () =>
h('div', {className: 'test_vcssom'}, 'addonVCSSOM')
)
159 changes: 159 additions & 0 deletions addon/__tests__/vcssom.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/* eslint-disable */
'use strict';

var cssToTree = require('../vcssom').cssToTree;

describe('cssToTree', function () {
test('exist', function () {
expect(cssToTree).toBeInstanceOf(Function);
});

test('simple object', () => {
var tree = {};
cssToTree(tree, {color: 'red'}, '&', '');

expect(tree).toEqual({
'': {
'&': {
color: 'red'
}
}
});
});

test('multiple properties', () => {
var tree = {};
var css = {
color: 'red',
border: '1px solid tomato',
textDecoration: 'underline,'
};
cssToTree(tree, css, '&', '');

expect(tree).toEqual({ '':
{ '&':
{ color: 'red',
border: '1px solid tomato',
textDecoration: 'underline,' } } });
});

test('nested selector', () => {
var tree = {};
var css = {
color: 'red',
svg: {
fill: 'green',
}
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({
'': {
'X svg': {
fill: 'green'
},
X: {
color: 'red'
}
}
});
});

test('nesting with single & pseudo selector', () => {
var tree = {};
var css = {
svg: {
fill: 'red',
},
'&:hover': {
fill: 'green',
}
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({"": {"X svg": {"fill": "red"}, "X:hover": {"fill": "green"}}});
});

test('more complicated nesting', () => {
var tree = {};
var css = {
border: '1px solid red',
fontFamily: 'monospace',
'&:hover': {
color: 'red',
},
'.global_class &': {
textDecoration: 'underline',
},
'& svg': {
fill: 'red',
},
'&:hover svg': {
fill: 'green',
}
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({ '':
{ X: { border: '1px solid red', fontFamily: 'monospace' },
'X:hover': { color: 'red' },
'.global_class X': { textDecoration: 'underline' },
'X svg': { fill: 'red' },
'X:hover svg': { fill: 'green' } } });
});

test('interpolates multiple ampersands', () => {
var tree = {};
var css = {
'&:hover,&:active': {
color: 'red',
},
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({ '': { 'X:hover,X:active': { color: 'red' } } });
});

test('interpolates multiple ampersands wither further nesting', () => {
var tree = {};
var css = {
'&:hover,&:active': {
svg: {
fill: 'red',
}
},
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({ '': { 'X:hover svg,X:active svg': { fill: 'red' } } });
});

test('supports media query', () => {
var tree = {};
var css = {
'@media screen': {
color: 'red',
}
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({ '@media screen': { X: { color: 'red' } } });
});

test('media query with other values', () => {
var tree = {};
var css = {
color: 'green',
'@media screen': {
color: 'red',
'&:hover': {
color: 'blue',
}
}
};
cssToTree(tree, css, 'X', '');

expect(tree).toEqual({ '': { X: { color: 'green' } },
'@media screen': { X: { color: 'red' }, 'X:hover': { color: 'blue' } } });
});
});
140 changes: 140 additions & 0 deletions addon/vcssom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
'use strict';

var cssToTree = function (tree, css, selector, prelude) {
var declarations = {};
var hasDeclarations = false;
var key, value;

for (key in css) {
value = css[key];
if (typeof value !== 'object') {
hasDeclarations = true;
declarations[key] = value;
}
}

if (hasDeclarations) {
if (!tree[prelude]) tree[prelude] = {};
tree[prelude][selector] = declarations;
}

for (key in css) {
value = css[key];
if (typeof value === 'object') {
if (key[0] === '@') {
cssToTree(tree, value, selector, key);
} else {
var hasCurrentSymbol = key.indexOf('&') > -1;
var selectorParts = selector.split(',');
if (hasCurrentSymbol) {
for (var i = 0; i < selectorParts.length; i++) {
selectorParts[i] = key.replace(/&/g, selectorParts[i]);
}
} else {
for (var i = 0; i < selectorParts.length; i++) {
selectorParts[i] = selectorParts[i] + ' ' + key;
}
}
cssToTree(tree, value, selectorParts.join(','), prelude);
}
}
}
};

exports.cssToTree = cssToTree;

exports.addon = function (renderer) {
if (process.env.NODE_ENV !== 'production') {
require('./__dev__/warnOnMissingDependencies')('pipe', renderer, ['createRule']); // cssom
}

function VRule (rule, decl) {
this.rule = rule;
this.decl = decl;
}

VRule.prototype.diff = function (newDecl) {
var oldDecl = this.decl;
var style = this.rule.style;
var property;

for (property in oldDecl)
if (newDecl[property] === undefined)
style.removeProperty(property);

for (property in newDecl) {
if (newDecl[property] !== oldDecl[property]) {
style.setProperty(property, newDecl[property]);
}
}

this.decl = newDecl;
};

function VSheet () {
/**
* {
* '<at-rule-prelude>': {
* '<selector>': {
* color: 'red
* }
* }
* }
*/
this.tree = {};
}

VSheet.prototype.diff = function (newTree) {
var sh = renderer.sh.sheet;
var msh = renderer.msh.sheet;
var oldTree = this.tree;

// Remove media queries not present in new tree.
for (var prelude in oldTree) {
if (newTree[prelude] === undefined) {
var rules = oldTree[prelude];
for (var selector in rules) {
msh.deleteRule(rules[selector].index);
}
}
}

for (var prelude in newTree) {
if (oldTree[prelude] === undefined) {
// Whole media query is new.
for (var selector in newTree[prelude]) {
var rule = new VRule(renderer.createRule(selector, prelude), {});
rule.diff(newTree[prelude][selector]);
newTree[prelude][selector] = rule;
}
} else {
// Old tree already has rules with this media query.
var oldRules = oldTree[prelude];
var newRules = newTree[prelude];

// Remove rules not present in new tree.
for (var selector in oldRules) {
if (!newRules[selector])
sh.deleteRule(oldRules[selector].index)
}

// Apply new rules.
for (var selector in newRules) {
var rule = oldRules[selector];
if (rule) {
rule.diff(newRules[selector]);
newRules[selector] = rule;
} else {
rule = new VRule(renderer.createRule(selector, prelude), {});
rule.diff(newRules[selector]);
newRules[selector] = rule;
}
}
}
}

this.tree = newTree;
};

renderer.VSheet = VSheet
};

0 comments on commit 63e27f2

Please sign in to comment.