-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcss-ns.js
More file actions
131 lines (115 loc) · 5.12 KB
/
css-ns.js
File metadata and controls
131 lines (115 loc) · 5.12 KB
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
module.exports = createCssNs;
module.exports.createCssNs = createCssNs;
module.exports.createOptions = createOptions;
module.exports.createReact = createReact;
module.exports.nsAuto = nsAuto;
module.exports.nsString = nsString;
module.exports.nsArray = nsArray;
module.exports.nsObject = nsObject;
module.exports.nsReactElement = nsReactElement;
function isString(x) {
return typeof x === 'string';
}
function isArray(x) {
return Array.isArray(x);
}
function isObject(x) {
return typeof x === 'object' && !isArray(x) && x !== null;
}
function isRegex(x) {
return x instanceof RegExp;
}
function isReactElement(opt, x) {
return opt.React && opt.React.isValidElement(x);
}
function assert(truthyValue, message) {
if (!truthyValue) throw new Error(message);
}
function assertOptionType(assertion, readableType, name, value) {
assert(assertion(value), 'The "' + name + '" option must be of type ' + readableType + ', got: ' + value);
return value;
}
var assertRegexOption = assertOptionType.bind(null, isRegex, 'RegExp');
var assertStringOption = assertOptionType.bind(null, isString, 'string');
var assertObjectOption = assertOptionType.bind(null, isObject, 'object');
function createOptions(raw) {
if (raw._cssNsOpts) return raw; // already processed, let's skip any extra work
if (isString(raw)) return createOptions({ namespace: raw }); // shorthand for just specifying the namespace
assert(isObject(raw), 'Options must be provided either as an object or a string, got: ' + raw);
return {
namespace: assertStringOption( 'namespace', raw.namespace).replace(/.*\/([\w-]+).*/, '$1'), // e.g. "/path/to/MyComponent.js" becomes "MyComponent"
include: assertRegexOption( 'include', raw.include || /^[a-z]/), // assume upper-cased classes are other components
exclude: assertRegexOption( 'exclude', raw.exclude || /^$/), // don't exclude anything by default (this regex will never match anything of relevance)
self: assertRegexOption( 'self', raw.self || /^this$/), // "this" references the current component directly
glue: assertStringOption( 'glue', raw.glue || '-'), // allows e.g. "MyComponent_foo" when set to "_"
React: raw.React && assertObjectOption('React', raw.React) || null, // passing in a React instance enables the React convenience methods
_cssNsOpts: true // simple flag signaling that options are already processed
};
}
function createCssNs(options) {
var opt = createOptions(options);
var ns = nsAuto.bind(null, opt);
if (opt.React) ns.React = createReact(opt, ns);
return ns;
}
function createReact(options, ns) {
var opt = createOptions(options);
ns = ns || nsAuto.bind(null, opt); // avoid creating another bound version of nsAuto() if our caller already provided one
assert(opt.React, 'React support must be explicitly enabled by providing the "React" option');
return Object.create(opt.React, { // inherit everything from standard React
createElement: { // ...except hijack createElement()
value: function(_, props) {
if (props) props.className = ns(props.className);
return opt.React.createElement.apply(opt.React, arguments);
}
}
});
}
function nsAuto(options, x) {
var opt = createOptions(options);
if (isReactElement(opt, x))
return nsReactElement(opt, x);
else if (isString(x))
return nsString(opt, x);
else if (isArray(x))
return nsArray(opt, x);
else if (isObject(x))
return nsObject(opt, x);
else // input was something we don't understand (e.g. a falsy value) -> pass it through
return x;
}
function nsString(options, string) {
assert(isString(string), 'nsString() expects string input, got: ' + string);
var opt = createOptions(options);
return string.split(/\s+/).map(function(cls) {
if (cls.match(opt.self))
return opt.namespace;
else if (cls.match(opt.include) && !cls.match(opt.exclude))
return opt.namespace + opt.glue + cls;
else
return cls;
}).join(' ').trim();
}
function nsArray(options, array) {
assert(isArray(array), 'nsArray() expects array input, got: ' + array);
var opt = createOptions(options);
return array
.filter(function(x) { return !!x })
.map(nsString.bind(null, opt))
.join(' ');
}
function nsObject(options, object) {
assert(isObject(object), 'nsObject() expects object input, got: ' + object);
var opt = createOptions(options);
return nsArray(opt, Object.keys(object).map(function(key) {
return object[key] ? key : null;
}));
}
function nsReactElement(options, el) {
if (isString(el)) return el; // we're mapping a text node -> leave it be
var opt = createOptions(options);
assert(isReactElement(opt, el), 'nsReactElement() expects a valid React element, got: ' + el);
var props = el.props.className ? { className: nsAuto(opt, el.props.className) } : el.props;
var children = opt.React.Children.map(el.props.children, nsReactElement.bind(null, opt));
return opt.React.cloneElement(el, props, children);
}