1 |
|
2 | import pseudos from './pseudos.js'
|
3 | import popular from './popular.js'
|
4 |
|
5 | import {
|
6 | classPrefix,
|
7 | createClass,
|
8 | setDebug,
|
9 | getSheet,
|
10 | insert
|
11 | } from './sheet'
|
12 |
|
13 | import {
|
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 |
|
32 | const shorts = Object.create(null)
|
33 |
|
34 | function bss(input, value) {
|
35 | const b = chain(bss)
|
36 | assign(b.__style, parse.apply(null, arguments))
|
37 | return b
|
38 | }
|
39 |
|
40 | function setProp(prop, value) {
|
41 | Object.defineProperty(bss, prop, {
|
42 | configurable: true,
|
43 | value
|
44 | })
|
45 | }
|
46 |
|
47 | Object.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 |
|
62 | setProp('setDebug', setDebug)
|
63 |
|
64 | setProp('$keyframes', keyframes)
|
65 | setProp('$media', $media)
|
66 | setProp('$import', $import)
|
67 | setProp('$nest', $nest)
|
68 | setProp('getSheet', getSheet)
|
69 | setProp('helper', helper)
|
70 | setProp('css', css)
|
71 | setProp('classPrefix', classPrefix)
|
72 |
|
73 | function 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 |
|
96 | cssProperties.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 |
|
115 | setProp('content', function Content(arg) {
|
116 | this.__style.content = '"' + arg + '"'
|
117 | return chain(this)
|
118 | })
|
119 |
|
120 | Object.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 |
|
129 | function $media(value, style) {
|
130 | if (value)
|
131 | add(this.__style, '@media ' + value, parse(style))
|
132 |
|
133 | return chain(this)
|
134 | }
|
135 |
|
136 | function $import(value) {
|
137 | if (value)
|
138 | insert('@import ' + value + ';', 0)
|
139 |
|
140 | return chain(this)
|
141 | }
|
142 |
|
143 | function $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 |
|
152 | function 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 |
|
163 | pseudos.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 |
|
171 | function 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 |
|
186 | function 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 |
|
195 | function addCss(selector, style) {
|
196 | objectToRules(parse(style), selector, '', true).forEach(insert)
|
197 | }
|
198 |
|
199 | function helper(name, styling) {
|
200 | if (arguments.length === 1)
|
201 | return Object.keys(name).forEach(key => helper(key, name[key]))
|
202 |
|
203 | delete bss[name]
|
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 |
|
227 | bss.helper('$animate', (value, props) =>
|
228 | bss.animation(bss.$keyframes(props) + ' ' + value)
|
229 | )
|
230 |
|
231 | function 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 |
|
239 | const 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 |
|
278 | let count = 0
|
279 | const keyframeCache = {}
|
280 |
|
281 | function 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 |
|
296 | function 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 |
|
312 | export default bss
|