source: node_modules/autoprefixer/lib/hacks/gradient.js@ 2058e5c

Last change on this file since 2058e5c was 2058e5c, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Working / before login

  • Property mode set to 100644
File size: 10.4 KB
Line 
1let parser = require('postcss-value-parser')
2
3let OldValue = require('../old-value')
4let utils = require('../utils')
5let Value = require('../value')
6
7let IS_DIRECTION = /top|left|right|bottom/gi
8
9class Gradient extends Value {
10 /**
11 * Do not add non-webkit prefixes for list-style and object
12 */
13 add(decl, prefix) {
14 let p = decl.prop
15 if (p.includes('mask')) {
16 if (prefix === '-webkit-' || prefix === '-webkit- old') {
17 return super.add(decl, prefix)
18 }
19 } else if (
20 p === 'list-style' ||
21 p === 'list-style-image' ||
22 p === 'content'
23 ) {
24 if (prefix === '-webkit-' || prefix === '-webkit- old') {
25 return super.add(decl, prefix)
26 }
27 } else {
28 return super.add(decl, prefix)
29 }
30 return undefined
31 }
32
33 /**
34 * Get div token from exists parameters
35 */
36 cloneDiv(params) {
37 for (let i of params) {
38 if (i.type === 'div' && i.value === ',') {
39 return i
40 }
41 }
42 return { after: ' ', type: 'div', value: ',' }
43 }
44
45 /**
46 * Change colors syntax to old webkit
47 */
48 colorStops(params) {
49 let result = []
50 for (let i = 0; i < params.length; i++) {
51 let pos
52 let param = params[i]
53 let item
54 if (i === 0) {
55 continue
56 }
57
58 let color = parser.stringify(param[0])
59 if (param[1] && param[1].type === 'word') {
60 pos = param[1].value
61 } else if (param[2] && param[2].type === 'word') {
62 pos = param[2].value
63 }
64
65 let stop
66 if (i === 1 && (!pos || pos === '0%')) {
67 stop = `from(${color})`
68 } else if (i === params.length - 1 && (!pos || pos === '100%')) {
69 stop = `to(${color})`
70 } else if (pos) {
71 stop = `color-stop(${pos}, ${color})`
72 } else {
73 stop = `color-stop(${color})`
74 }
75
76 let div = param[param.length - 1]
77 params[i] = [{ type: 'word', value: stop }]
78 if (div.type === 'div' && div.value === ',') {
79 item = params[i].push(div)
80 }
81 result.push(item)
82 }
83 return result
84 }
85
86 /**
87 * Change new direction to old
88 */
89 convertDirection(params) {
90 if (params.length > 0) {
91 if (params[0].value === 'to') {
92 this.fixDirection(params)
93 } else if (params[0].value.includes('deg')) {
94 this.fixAngle(params)
95 } else if (this.isRadial(params)) {
96 this.fixRadial(params)
97 }
98 }
99 return params
100 }
101
102 /**
103 * Add 90 degrees
104 */
105 fixAngle(params) {
106 let first = params[0].value
107 first = parseFloat(first)
108 first = Math.abs(450 - first) % 360
109 first = this.roundFloat(first, 3)
110 params[0].value = `${first}deg`
111 }
112
113 /**
114 * Replace `to top left` to `bottom right`
115 */
116 fixDirection(params) {
117 params.splice(0, 2)
118
119 for (let param of params) {
120 if (param.type === 'div') {
121 break
122 }
123 if (param.type === 'word') {
124 param.value = this.revertDirection(param.value)
125 }
126 }
127 }
128
129 /**
130 * Fix radial direction syntax
131 */
132 fixRadial(params) {
133 let first = []
134 let second = []
135 let a, b, c, i, next
136
137 for (i = 0; i < params.length - 2; i++) {
138 a = params[i]
139 b = params[i + 1]
140 c = params[i + 2]
141 if (a.type === 'space' && b.value === 'at' && c.type === 'space') {
142 next = i + 3
143 break
144 } else {
145 first.push(a)
146 }
147 }
148
149 let div
150 for (i = next; i < params.length; i++) {
151 if (params[i].type === 'div') {
152 div = params[i]
153 break
154 } else {
155 second.push(params[i])
156 }
157 }
158
159 params.splice(0, i, ...second, div, ...first)
160 }
161
162 /**
163 * Look for at word
164 */
165 isRadial(params) {
166 let state = 'before'
167 for (let param of params) {
168 if (state === 'before' && param.type === 'space') {
169 state = 'at'
170 } else if (state === 'at' && param.value === 'at') {
171 state = 'after'
172 } else if (state === 'after' && param.type === 'space') {
173 return true
174 } else if (param.type === 'div') {
175 break
176 } else {
177 state = 'before'
178 }
179 }
180 return false
181 }
182
183 /**
184 * Replace old direction to new
185 */
186 newDirection(params) {
187 if (params[0].value === 'to') {
188 return params
189 }
190 IS_DIRECTION.lastIndex = 0 // reset search index of global regexp
191 if (!IS_DIRECTION.test(params[0].value)) {
192 return params
193 }
194
195 params.unshift(
196 {
197 type: 'word',
198 value: 'to'
199 },
200 {
201 type: 'space',
202 value: ' '
203 }
204 )
205
206 for (let i = 2; i < params.length; i++) {
207 if (params[i].type === 'div') {
208 break
209 }
210 if (params[i].type === 'word') {
211 params[i].value = this.revertDirection(params[i].value)
212 }
213 }
214
215 return params
216 }
217
218 /**
219 * Normalize angle
220 */
221 normalize(nodes, gradientName) {
222 if (!nodes[0]) return nodes
223
224 if (/-?\d+(.\d+)?grad/.test(nodes[0].value)) {
225 nodes[0].value = this.normalizeUnit(nodes[0].value, 400)
226 } else if (/-?\d+(.\d+)?rad/.test(nodes[0].value)) {
227 nodes[0].value = this.normalizeUnit(nodes[0].value, 2 * Math.PI)
228 } else if (/-?\d+(.\d+)?turn/.test(nodes[0].value)) {
229 nodes[0].value = this.normalizeUnit(nodes[0].value, 1)
230 } else if (nodes[0].value.includes('deg')) {
231 let num = parseFloat(nodes[0].value)
232 num = (num % 360 + 360) % 360
233 nodes[0].value = `${num}deg`
234 }
235
236 if (
237 gradientName === 'linear-gradient' ||
238 gradientName === 'repeating-linear-gradient'
239 ) {
240 let direction = nodes[0].value
241
242 // Unitless zero for `<angle>` values are allowed in CSS gradients and transforms.
243 // Spec: https://github.com/w3c/csswg-drafts/commit/602789171429b2231223ab1e5acf8f7f11652eb3
244 if (direction === '0deg' || direction === '0') {
245 nodes = this.replaceFirst(nodes, 'to', ' ', 'top')
246 } else if (direction === '90deg') {
247 nodes = this.replaceFirst(nodes, 'to', ' ', 'right')
248 } else if (direction === '180deg') {
249 nodes = this.replaceFirst(nodes, 'to', ' ', 'bottom') // default value
250 } else if (direction === '270deg') {
251 nodes = this.replaceFirst(nodes, 'to', ' ', 'left')
252 }
253 }
254
255 return nodes
256 }
257
258 /**
259 * Convert angle unit to deg
260 */
261 normalizeUnit(str, full) {
262 let num = parseFloat(str)
263 let deg = (num / full) * 360
264 return `${deg}deg`
265 }
266
267 /**
268 * Remove old WebKit gradient too
269 */
270 old(prefix) {
271 if (prefix === '-webkit-') {
272 let type
273 if (this.name === 'linear-gradient') {
274 type = 'linear'
275 } else if (this.name === 'repeating-linear-gradient') {
276 type = 'repeating-linear'
277 } else if (this.name === 'repeating-radial-gradient') {
278 type = 'repeating-radial'
279 } else {
280 type = 'radial'
281 }
282 let string = '-gradient'
283 let regexp = utils.regexp(
284 `-webkit-(${type}-gradient|gradient\\(\\s*${type})`,
285 false
286 )
287
288 return new OldValue(this.name, prefix + this.name, string, regexp)
289 } else {
290 return super.old(prefix)
291 }
292 }
293
294 /**
295 * Change direction syntax to old webkit
296 */
297 oldDirection(params) {
298 let div = this.cloneDiv(params[0])
299
300 if (params[0][0].value !== 'to') {
301 return params.unshift([
302 { type: 'word', value: Gradient.oldDirections.bottom },
303 div
304 ])
305 } else {
306 let words = []
307 for (let node of params[0].slice(2)) {
308 if (node.type === 'word') {
309 words.push(node.value.toLowerCase())
310 }
311 }
312
313 words = words.join(' ')
314 let old = Gradient.oldDirections[words] || words
315
316 params[0] = [{ type: 'word', value: old }, div]
317 return params[0]
318 }
319 }
320
321 /**
322 * Convert to old webkit syntax
323 */
324 oldWebkit(node) {
325 let { nodes } = node
326 let string = parser.stringify(node.nodes)
327
328 if (this.name !== 'linear-gradient') {
329 return false
330 }
331 if (nodes[0] && nodes[0].value.includes('deg')) {
332 return false
333 }
334 if (
335 string.includes('px') ||
336 string.includes('-corner') ||
337 string.includes('-side')
338 ) {
339 return false
340 }
341
342 let params = [[]]
343 for (let i of nodes) {
344 params[params.length - 1].push(i)
345 if (i.type === 'div' && i.value === ',') {
346 params.push([])
347 }
348 }
349
350 this.oldDirection(params)
351 this.colorStops(params)
352
353 node.nodes = []
354 for (let param of params) {
355 node.nodes = node.nodes.concat(param)
356 }
357
358 node.nodes.unshift(
359 { type: 'word', value: 'linear' },
360 this.cloneDiv(node.nodes)
361 )
362 node.value = '-webkit-gradient'
363
364 return true
365 }
366
367 /**
368 * Change degrees for webkit prefix
369 */
370 replace(string, prefix) {
371 let ast = parser(string)
372 for (let node of ast.nodes) {
373 let gradientName = this.name // gradient name
374 if (node.type === 'function' && node.value === gradientName) {
375 node.nodes = this.newDirection(node.nodes)
376 node.nodes = this.normalize(node.nodes, gradientName)
377 if (prefix === '-webkit- old') {
378 let changes = this.oldWebkit(node)
379 if (!changes) {
380 return false
381 }
382 } else {
383 node.nodes = this.convertDirection(node.nodes)
384 node.value = prefix + node.value
385 }
386 }
387 }
388 return ast.toString()
389 }
390
391 /**
392 * Replace first token
393 */
394 replaceFirst(params, ...words) {
395 let prefix = words.map(i => {
396 if (i === ' ') {
397 return { type: 'space', value: i }
398 }
399 return { type: 'word', value: i }
400 })
401 return prefix.concat(params.slice(1))
402 }
403
404 revertDirection(word) {
405 return Gradient.directions[word.toLowerCase()] || word
406 }
407
408 /**
409 * Round float and save digits under dot
410 */
411 roundFloat(float, digits) {
412 return parseFloat(float.toFixed(digits))
413 }
414}
415
416Gradient.names = [
417 'linear-gradient',
418 'repeating-linear-gradient',
419 'radial-gradient',
420 'repeating-radial-gradient'
421]
422
423Gradient.directions = {
424 bottom: 'top',
425 left: 'right',
426 right: 'left',
427 top: 'bottom' // default value
428}
429
430// Direction to replace
431Gradient.oldDirections = {
432 'bottom': 'left top, left bottom',
433 'bottom left': 'right top, left bottom',
434 'bottom right': 'left top, right bottom',
435 'left': 'right top, left top',
436
437 'left bottom': 'right top, left bottom',
438 'left top': 'right bottom, left top',
439 'right': 'left top, right top',
440 'right bottom': 'left top, right bottom',
441 'right top': 'left bottom, right top',
442 'top': 'left bottom, left top',
443 'top left': 'right bottom, left top',
444 'top right': 'left bottom, right top'
445}
446
447module.exports = Gradient
Note: See TracBrowser for help on using the repository browser.