UNPKG

7 kBJavaScriptView Raw
1/* eslint no-invalid-this: 0 */
2import pseudos from './pseudos.js'
3import popular from './popular.js'
4
5import {
6 classPrefix,
7 createClass,
8 setDebug,
9 getSheet,
10 insert
11} from './sheet'
12
13import {
14 hyphenToCamelCase,
15 vendorValuePrefix,
16 lowercaseFirst,
17 objectToRules,
18 selectorSplit,
19 cssProperties,
20 stylesToCss,
21 vendorRegex,
22 formatValue,
23 vendorMap,
24 sanitize,
25 initials,
26 memoize,
27 assign,
28 addPx,
29 add
30} from './utils'
31
32const shorts = Object.create(null)
33
34function bss(input, value) {
35 const b = chain(bss)
36 assign(b.__style, parse.apply(null, arguments))
37 return b
38}
39
40function setProp(prop, value) {
41 Object.defineProperty(bss, prop, {
42 configurable: true,
43 value
44 })
45}
46
47Object.defineProperties(bss, {
48 __style: {
49 configurable: true,
50 writable: true,
51 value: {}
52 },
53 valueOf: {
54 configurable: true,
55 writable: true,
56 value: function ValueOf() {
57 return '.' + this.class
58 }
59 }
60})
61
62setProp('setDebug', setDebug)
63
64setProp('$keyframes', keyframes)
65setProp('$media', $media)
66setProp('$import', $import)
67setProp('$nest', $nest)
68setProp('getSheet', getSheet)
69setProp('helper', helper)
70setProp('css', css)
71setProp('classPrefix', classPrefix)
72
73function chain(instance) {
74 const newInstance = Object.create(bss, {
75 __style: {
76 value: instance.__style
77 },
78 style: {
79 enumerable: true,
80 get: function() {
81 return Object.keys(this.__style).reduce((acc, key) => {
82 if (typeof this.__style[key] === 'number' || typeof this.__style[key] === 'string')
83 acc[key.replace(/^!/, '')] = this.__style[key]
84 return acc
85 }, {})
86 }
87 }
88 })
89
90 if (instance === bss)
91 bss.__style = {}
92
93 return newInstance
94}
95
96cssProperties.forEach(prop => {
97 const vendor = prop.match(vendorRegex)
98 if (vendor) {
99 const unprefixed = lowercaseFirst(prop.replace(vendorRegex, '$2'))
100 if (cssProperties.indexOf(unprefixed) === -1) {
101 if (unprefixed === 'flexDirection')
102 vendorValuePrefix.flex = '-' + vendor[1].toLowerCase() + '-flex'
103
104 vendorMap[unprefixed] = prop
105 setProp(unprefixed, setter(prop))
106 setProp(short(unprefixed), bss[unprefixed])
107 return
108 }
109 }
110
111 setProp(prop, setter(prop))
112 setProp(short(prop), bss[prop])
113})
114
115setProp('content', function Content(arg) {
116 this.__style.content = '"' + arg + '"'
117 return chain(this)
118})
119
120Object.defineProperty(bss, 'class', {
121 set: function(value) {
122 this.__class = value
123 },
124 get: function() {
125 return this.__class || createClass(this.__style)
126 }
127})
128
129function $media(value, style) {
130 if (value)
131 add(this.__style, '@media ' + value, parse(style))
132
133 return chain(this)
134}
135
136function $import(value) {
137 if (value)
138 insert('@import ' + value + ';', 0)
139
140 return chain(this)
141}
142
143function $nest(selector, properties) {
144 if (arguments.length === 1)
145 Object.keys(selector).forEach(x => addNest(this.__style, x, selector[x]))
146 else if (selector)
147 addNest(this.__style, selector, properties)
148
149 return chain(this)
150}
151
152function addNest(style, selector, properties) {
153 add(
154 style,
155 selector.split(selectorSplit).map(x => {
156 x = x.trim()
157 return (x.charAt(0) === ':' || x.charAt(0) === '[' ? '' : ' ') + x
158 }).join(',&'),
159 parse(properties)
160 )
161}
162
163pseudos.forEach(name =>
164 setProp('$' + hyphenToCamelCase(name.replace(/:/g, '')), function Pseudo(value, b) {
165 if (value || b)
166 add(this.__style, name + (b ? '(' + value + ')' : ''), parse(b || value))
167 return chain(this)
168 })
169)
170
171function setter(prop) {
172 return function CssProperty(value) {
173 if (!value && value !== 0) {
174 delete this.__style[prop]
175 } else if (arguments.length > 0) {
176 add(this.__style, prop, arguments.length === 1
177 ? formatValue(prop, value)
178 : Array.prototype.slice.call(arguments).map(v => addPx(prop, v)).join(' ')
179 )
180 }
181
182 return chain(this)
183 }
184}
185
186function css(selector, style) {
187 if (arguments.length === 1)
188 Object.keys(selector).forEach(key => addCss(key, selector[key]))
189 else
190 addCss(selector, style)
191
192 return chain(this)
193}
194
195function addCss(selector, style) {
196 objectToRules(parse(style), selector, '', true).forEach(insert)
197}
198
199function helper(name, styling) {
200 if (arguments.length === 1)
201 return Object.keys(name).forEach(key => helper(key, name[key]))
202
203 delete bss[name] // Needed to avoid weird get calls in chrome
204
205 if (typeof styling === 'function') {
206 helper[name] = styling
207 Object.defineProperty(bss, name, {
208 configurable: true,
209 value: function Helper() {
210 const result = styling.apply(null, arguments)
211 assign(this.__style, result.__style)
212 return chain(this)
213 }
214 })
215 } else {
216 helper[name] = parse(styling)
217 Object.defineProperty(bss, name, {
218 configurable: true,
219 get: function() {
220 assign(this.__style, parse(styling))
221 return chain(this)
222 }
223 })
224 }
225}
226
227bss.helper('$animate', (value, props) =>
228 bss.animation(bss.$keyframes(props) + ' ' + value)
229)
230
231function short(prop) {
232 const acronym = initials(prop)
233 , short = popular[acronym] && popular[acronym] !== prop ? prop : acronym
234
235 shorts[short] = prop
236 return short
237}
238
239const stringToObject = memoize(string => {
240 let last = ''
241 , prev
242
243 return string.trim().split(/;|\n/).reduce((acc, line) => {
244 line = last + line.trim()
245 last = line.charAt(line.length - 1) === ',' ? line : ''
246 if (last)
247 return acc
248
249 if (line.charAt(0) === ',') {
250 acc[prev] += line
251 return acc
252 }
253
254 const [key, ...tokens] = line.replace(/[ :]+/, ' ').split(' ')
255
256 if (!key)
257 return acc
258
259 const cssVar = key.charAt(0) === '-' && key.charAt(1) === '-'
260 , prop = cssVar
261 ? key
262 : hyphenToCamelCase(key)
263
264 prev = shorts[prop] || prop
265
266 if (prop in helper) {
267 typeof helper[prop] === 'function'
268 ? assign(acc, helper[prop](...tokens).__style)
269 : assign(acc, helper[prop])
270 } else if (tokens.length > 0) {
271 add(acc, prev, tokens.map(t => cssVar ? t : addPx(prev, t)).join(' '))
272 }
273
274 return acc
275 }, {})
276})
277
278let count = 0
279const keyframeCache = {}
280
281function keyframes(props) {
282 const content = Object.keys(props).reduce((acc, key) =>
283 acc + key + '{' + stylesToCss(parse(props[key])) + '}'
284 , '')
285
286 if (content in keyframeCache)
287 return keyframeCache[content]
288
289 const name = classPrefix + count++
290 keyframeCache[content] = name
291 insert('@keyframes ' + name + '{' + content + '}')
292
293 return name
294}
295
296function parse(input, value) {
297 if (typeof input === 'string') {
298 if (typeof value === 'string' || typeof value === 'number')
299 return ({ [input] : value })
300
301 return stringToObject(input)
302 } else if (Array.isArray(input) && typeof input[0] === 'string') {
303 let str = ''
304 for (let i = 0; i < input.length; i++)
305 str += input[i] + (arguments[i + 1] || '')
306 return stringToObject(str)
307 }
308
309 return input.__style || sanitize(input)
310}
311
312export default bss