Skip to content

Commit b3c5eb8

Browse files
committed
add enhanced styleset selectors
1 parent 404b376 commit b3c5eb8

File tree

3 files changed

+152
-96
lines changed

3 files changed

+152
-96
lines changed

api.md

+37
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,43 @@ var stylesheet = require('...').register({
247247

248248
Sections
249249
----------
250+
### Styleset Path Selectors
251+
Multiple stylesets can be included with a single styleset reference.
252+
253+
* Each styleset reference should be separated with a space or comma.
254+
* Nested stylesets should be sparated with ```.```.
255+
* multiple nested styleset references have a shorthand of parent[child1 {space or comma} child2 ...]
256+
257+
For example
258+
```
259+
foo, a[b c] d[e,f], bar
260+
```
261+
would result in the following stylets
262+
```
263+
['foo', 'a.b', 'a.c', 'd.e', 'd.f', 'bar']
264+
```
265+
Matching a stylesheet like the following
266+
```
267+
{
268+
foo: { ... },
269+
270+
a: {
271+
attributes: { ... }
272+
b: { ... },
273+
c: { ... }
274+
},
275+
276+
d: {
277+
attributes: { ... },
278+
e: { ... },
279+
f: { ... }
280+
},
281+
282+
bar: { ... }
283+
}
284+
```
285+
286+
250287
### Examples
251288

252289
#### Registering mixins and variables

react-css-builder.js

+114-96
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
// utility / helper functions
22

3+
// iterate the attributes of an object
4+
function _each(obj, callback) {
5+
for (var name in obj) {
6+
if (obj.hasOwnProperty(name)) {
7+
callback(obj[name], name);
8+
}
9+
}
10+
}
11+
312
// extend the attributes of src into target
413
function _extend(target, src) {
514
if (src) {
6-
for (var name in src) {
7-
if (src.hasOwnProperty(name)) {
8-
target[name] = src[name];
9-
}
10-
}
15+
_each(src, function(value, name) {
16+
target[name] = value;
17+
});
1118
}
1219
return target;
1320
}
@@ -17,16 +24,16 @@
1724
return typeof obj === 'function';
1825
}
1926

20-
function _isDeep(obj) {
21-
for (var name in obj) {
22-
if (obj.hasOwnProperty(name)) {
23-
if (typeof obj[name] === 'object') {
24-
return true;
25-
}
26-
}
27-
}
27+
// return true if obj is a string
28+
function _isString(obj) {
29+
return typeof obj === 'string';
2830
}
2931

32+
// create and return the class
33+
function _createClass(constructor, attributes) {
34+
_extend(constructor.prototype, attributes);
35+
return constructor;
36+
}
3037

3138
/**
3239
* Return a style set function that should be executed as
@@ -72,60 +79,58 @@
7279

7380
/**
7481
* Normalize all stylesheet definitions
82+
* - styles: the user provided stylesheet
83+
* - builder: the associated Builder
7584
*/
7685
function normalizeStyles(styles, builder) {
77-
for (var key in styles) {
78-
if (styles.hasOwnProperty(key)) {
79-
builder._styles[key] = normalizeStyleAttributes(styles[key], builder);
80-
}
81-
}
86+
_each(styles, function(style, key) {
87+
builder._styles[key] = normalizeStyleAttributes(style, builder);
88+
});
8289
}
8390

8491
/**
8592
* Normalize the styleset attributes when registering stylesets.
8693
* Recurse function for normalizeStyles
94+
* - styleset: the style attributes for a particular style class
95+
* - builder: the associated Builder
8796
*/
8897
function normalizeStyleAttributes(styleset, builder) {
8998
var name;
9099
if (_isFunction(styleset)) {
100+
// user provided function so we need to give them the css context to work with
91101
return function(varRetriever) {
92102
return styleset.call(varRetriever, new StyleContext(varRetriever, builder));
93103
};
94104
}
95105

96-
if (styleset.attributes) {
97-
// has nesting
98-
var rtn = normalizeStyleAttributes(styleset.attributes);
99-
for (name in styleset) {
100-
if (styleset.hasOwnProperty(name) && name !== 'attributes') {
101-
rtn[name] = normalizeStyleAttributes(styleset[name], builder);
106+
var attr = styleset.attributes;
107+
if (attr) {
108+
// any nesting parent *must* include the "attributes" value
109+
var rtn = normalizeStyleAttributes(attr);
110+
_each(styleset, function(attr, name) {
111+
if (name !== 'attributes') {
112+
rtn[name] = normalizeStyleAttributes(attr, builder);
102113
}
103-
}
114+
});
104115
return rtn;
105116
} else {
106-
if (_isDeep(styleset)) {
107-
// nesting container
108-
for (name in styleset) {
109-
if (styleset.hasOwnProperty(name)) {
110-
styleset[name] = normalizeStyleAttributes(styleset[name], builder);
111-
}
112-
}
113-
return styleset;
114-
} else {
115-
// simple attributes
116-
return function() { return styleset; };
117-
}
117+
// simple attributes
118+
return function() { return styleset; };
118119
}
119120
}
120121

121122

122-
var Builder = function(parent) {
123+
/**
124+
* The object returned when calling require('react-css-builder').register('...')
125+
*/
126+
var Builder = _createClass(function(parent) {
123127
this._vars = {};
124128
this._mixins = {};
125129
this._styles = {};
126130
this.parent = parent;
127-
};
128-
_extend(Builder.prototype, {
131+
}, {
132+
133+
// return
129134
css: function(paths) {
130135
return new StyleSelector(paths, this).css();
131136
},
@@ -144,7 +149,7 @@
144149
},
145150

146151
vars: function(vars) {
147-
if (typeof vars === 'string') {
152+
if (_isString(vars)) {
148153
return this._vars[vars] || (this.parent && this.parent.vars(vars));
149154
}
150155

@@ -154,59 +159,21 @@
154159
});
155160

156161

157-
// globals
158-
var builders = {},
159-
main = new Builder();
160-
function mainFunc(name) {
161-
return function() {
162-
main[name].apply(main, arguments);
163-
};
164-
}
165-
166-
var root = {
167-
register: function(namespace, _styles) {
168-
if (!_styles) {
169-
_styles = namespace;
170-
namespace = undefined;
171-
}
172-
173-
var builder = namespace && builders[namespace];
174-
if (!builder) {
175-
builder = new Builder(main);
176-
if (namespace) {
177-
builder.namespace = namespace;
178-
builders[namespace] = builder;
179-
}
180-
}
181-
182-
if (_isFunction(_styles)) {
183-
_styles = _styles(rtn);
184-
}
185-
normalizeStyles(_styles, builder);
186-
return builder;
187-
},
188-
189-
vars: mainFunc('vars'),
190-
191-
mixin: mainFunc('mixin')
192-
};
193-
194162
/**
195163
* Class used as the return response from calling "css" on exports.
196164
* Allows for chained commands to be completed by the "val" method
197165
* to return the final styleset values.
198166
*/
199-
var StyleSelector = function(paths, builder) {
200-
this.paths = normalizePaths(paths);
167+
var StyleSelector = _createClass(function(path, builder) {
168+
this.paths = normalizePaths(path);
201169
this.builder = builder;
202170
var self = this;
203171
this.varRetriever = {
204172
get: function(key) {
205173
return (self._vars && self._vars[key]) || self.builder.vars(key);
206174
}
207175
};
208-
};
209-
_extend(StyleSelector.prototype, {
176+
}, {
210177
attr: function(attrs) {
211178
this._attrs = _attrs;
212179
return this;
@@ -222,27 +189,49 @@
222189

223190
var attrs = {};
224191
for (var i=0; i<this.paths.length; i++) {
225-
return getStyleSet(this.paths[i], this.builder)(this.varRetriever);
192+
_.extend(attrs, getStyleSet(this.paths[i], this.builder)(this.varRetriever));
226193
}
227194
_.extend(attrs, this._attrs);
228195
return attrs;
229196
}
230197
});
231198

232-
function normalizePaths(paths) {
233-
return paths && (Array.isArray(paths) ? paths : paths.split(/\s+/));
199+
var pathCache = {},
200+
nestingMatchPattern = /[^\s,]+\s*\[[^\]]+\]/g,
201+
nestingChildPattern = /^([^\[\s]+)\s*\[([^\]]+)/;
202+
203+
/**
204+
* Normalize the css selector path
205+
* multiple classes can be included and separated with whitespace or comma
206+
* multiple nested classes have a shorthand of parent[child1 child2 ...] (children separated with space or comma)
207+
* For example foo, a[b c] d[e,f], bar = ['foo', 'a.b', 'a.c', 'd.e', 'd.f', 'bar']
208+
*/
209+
function normalizePaths(path) {
210+
var rtn = pathCache[path];
211+
if (!rtn) {
212+
var result = path.replace(nestingMatchPattern, function(val) {
213+
var match = val.match(nestingChildPattern),
214+
parts = match[2].split(/[\s,]+/g),
215+
rtn = '';
216+
for (var i=0; i<parts.length; i++) {
217+
rtn += (' ' + match[1] + '.' + parts[i]);
218+
}
219+
return rtn;
220+
});
221+
rtn = pathCache[path] = result.split(/[,\s]+/g);
222+
}
223+
return rtn;
234224
}
235-
225+
236226

237227
/**
238228
* The style context object provided to styleset functions
239229
*/
240-
var StyleContext = function(varRetriever, builder) {
230+
var StyleContext = _createClass(function(varRetriever, builder) {
241231
this.varRetriever = varRetriever;
242232
this.builder = builder;
243233
this.attrs = {};
244-
};
245-
_extend(StyleContext.prototype, {
234+
}, {
246235
include: function(path) {
247236
_extend(this.attrs, getStyleSet(path, this.builder)(this.varRetriever));
248237
return this;
@@ -257,13 +246,42 @@
257246
return this;
258247
},
259248
val: function(attr) {
260-
if (attr) {
261-
return _extend(this.attrs, attr);
262-
} else {
263-
return this.attrs;
264-
}
265-
249+
return _extend(this.attrs, attr);
266250
}
267251
});
268252

269-
module.exports = root;
253+
254+
// global cache
255+
var builders = {},
256+
main = new Builder();
257+
258+
// wrapper function to ensure the context is the main Builder
259+
function mainFunc(name) {
260+
return function() {
261+
main[name].apply(main, arguments);
262+
};
263+
}
264+
265+
module.exports = {
266+
register: function(namespace, _styles) {
267+
if (!_styles) {
268+
_styles = namespace;
269+
namespace = undefined;
270+
}
271+
272+
var builder = namespace && builders[namespace];
273+
if (!builder) {
274+
builder = new Builder(main);
275+
if (namespace) {
276+
builder.namespace = namespace;
277+
builders[namespace] = builder;
278+
}
279+
}
280+
281+
normalizeStyles(_styles, builder);
282+
return builder;
283+
},
284+
285+
vars: mainFunc('vars'),
286+
mixin: mainFunc('mixin')
287+
};

test/test.js

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var hierarchyTestCss = css.register('foo', {
2727

2828
var hierarchyTestCss = css.register('hierarchy', {
2929
top: {
30+
attributes: {},
3031
bottom: {
3132
attributes: {
3233
test: 1

0 commit comments

Comments
 (0)