| let parser = require('postcss-value-parser') |
| let range = require('normalize-range') |
|
|
| let OldValue = require('../old-value') |
| let Value = require('../value') |
| let utils = require('../utils') |
|
|
| let IS_DIRECTION = /top|left|right|bottom/gi |
|
|
| class Gradient extends Value { |
| |
| |
| |
| add(decl, prefix) { |
| let p = decl.prop |
| if (p.includes('mask')) { |
| if (prefix === '-webkit-' || prefix === '-webkit- old') { |
| return super.add(decl, prefix) |
| } |
| } else if ( |
| p === 'list-style' || |
| p === 'list-style-image' || |
| p === 'content' |
| ) { |
| if (prefix === '-webkit-' || prefix === '-webkit- old') { |
| return super.add(decl, prefix) |
| } |
| } else { |
| return super.add(decl, prefix) |
| } |
| return undefined |
| } |
|
|
| |
| |
| |
| cloneDiv(params) { |
| for (let i of params) { |
| if (i.type === 'div' && i.value === ',') { |
| return i |
| } |
| } |
| return { after: ' ', type: 'div', value: ',' } |
| } |
|
|
| |
| |
| |
| colorStops(params) { |
| let result = [] |
| for (let i = 0; i < params.length; i++) { |
| let pos |
| let param = params[i] |
| let item |
| if (i === 0) { |
| continue |
| } |
|
|
| let color = parser.stringify(param[0]) |
| if (param[1] && param[1].type === 'word') { |
| pos = param[1].value |
| } else if (param[2] && param[2].type === 'word') { |
| pos = param[2].value |
| } |
|
|
| let stop |
| if (i === 1 && (!pos || pos === '0%')) { |
| stop = `from(${color})` |
| } else if (i === params.length - 1 && (!pos || pos === '100%')) { |
| stop = `to(${color})` |
| } else if (pos) { |
| stop = `color-stop(${pos}, ${color})` |
| } else { |
| stop = `color-stop(${color})` |
| } |
|
|
| let div = param[param.length - 1] |
| params[i] = [{ type: 'word', value: stop }] |
| if (div.type === 'div' && div.value === ',') { |
| item = params[i].push(div) |
| } |
| result.push(item) |
| } |
| return result |
| } |
|
|
| |
| |
| |
| convertDirection(params) { |
| if (params.length > 0) { |
| if (params[0].value === 'to') { |
| this.fixDirection(params) |
| } else if (params[0].value.includes('deg')) { |
| this.fixAngle(params) |
| } else if (this.isRadial(params)) { |
| this.fixRadial(params) |
| } |
| } |
| return params |
| } |
|
|
| |
| |
| |
| fixAngle(params) { |
| let first = params[0].value |
| first = parseFloat(first) |
| first = Math.abs(450 - first) % 360 |
| first = this.roundFloat(first, 3) |
| params[0].value = `${first}deg` |
| } |
|
|
| |
| |
| |
| fixDirection(params) { |
| params.splice(0, 2) |
|
|
| for (let param of params) { |
| if (param.type === 'div') { |
| break |
| } |
| if (param.type === 'word') { |
| param.value = this.revertDirection(param.value) |
| } |
| } |
| } |
|
|
| |
| |
| |
| fixRadial(params) { |
| let first = [] |
| let second = [] |
| let a, b, c, i, next |
|
|
| for (i = 0; i < params.length - 2; i++) { |
| a = params[i] |
| b = params[i + 1] |
| c = params[i + 2] |
| if (a.type === 'space' && b.value === 'at' && c.type === 'space') { |
| next = i + 3 |
| break |
| } else { |
| first.push(a) |
| } |
| } |
|
|
| let div |
| for (i = next; i < params.length; i++) { |
| if (params[i].type === 'div') { |
| div = params[i] |
| break |
| } else { |
| second.push(params[i]) |
| } |
| } |
|
|
| params.splice(0, i, ...second, div, ...first) |
| } |
|
|
| |
| |
| |
| isRadial(params) { |
| let state = 'before' |
| for (let param of params) { |
| if (state === 'before' && param.type === 'space') { |
| state = 'at' |
| } else if (state === 'at' && param.value === 'at') { |
| state = 'after' |
| } else if (state === 'after' && param.type === 'space') { |
| return true |
| } else if (param.type === 'div') { |
| break |
| } else { |
| state = 'before' |
| } |
| } |
| return false |
| } |
|
|
| |
| |
| |
| newDirection(params) { |
| if (params[0].value === 'to') { |
| return params |
| } |
| IS_DIRECTION.lastIndex = 0 |
| if (!IS_DIRECTION.test(params[0].value)) { |
| return params |
| } |
|
|
| params.unshift( |
| { |
| type: 'word', |
| value: 'to' |
| }, |
| { |
| type: 'space', |
| value: ' ' |
| } |
| ) |
|
|
| for (let i = 2; i < params.length; i++) { |
| if (params[i].type === 'div') { |
| break |
| } |
| if (params[i].type === 'word') { |
| params[i].value = this.revertDirection(params[i].value) |
| } |
| } |
|
|
| return params |
| } |
|
|
| |
| |
| |
| normalize(nodes, gradientName) { |
| if (!nodes[0]) return nodes |
|
|
| if (/-?\d+(.\d+)?grad/.test(nodes[0].value)) { |
| nodes[0].value = this.normalizeUnit(nodes[0].value, 400) |
| } else if (/-?\d+(.\d+)?rad/.test(nodes[0].value)) { |
| nodes[0].value = this.normalizeUnit(nodes[0].value, 2 * Math.PI) |
| } else if (/-?\d+(.\d+)?turn/.test(nodes[0].value)) { |
| nodes[0].value = this.normalizeUnit(nodes[0].value, 1) |
| } else if (nodes[0].value.includes('deg')) { |
| let num = parseFloat(nodes[0].value) |
| num = range.wrap(0, 360, num) |
| nodes[0].value = `${num}deg` |
| } |
|
|
| if ( |
| gradientName === 'linear-gradient' || |
| gradientName === 'repeating-linear-gradient' |
| ) { |
| let direction = nodes[0].value |
|
|
| |
| |
| if (direction === '0deg' || direction === '0') { |
| nodes = this.replaceFirst(nodes, 'to', ' ', 'top') |
| } else if (direction === '90deg') { |
| nodes = this.replaceFirst(nodes, 'to', ' ', 'right') |
| } else if (direction === '180deg') { |
| nodes = this.replaceFirst(nodes, 'to', ' ', 'bottom') |
| } else if (direction === '270deg') { |
| nodes = this.replaceFirst(nodes, 'to', ' ', 'left') |
| } |
| } |
|
|
| return nodes |
| } |
|
|
| |
| |
| |
| normalizeUnit(str, full) { |
| let num = parseFloat(str) |
| let deg = (num / full) * 360 |
| return `${deg}deg` |
| } |
|
|
| |
| |
| |
| old(prefix) { |
| if (prefix === '-webkit-') { |
| let type |
| if (this.name === 'linear-gradient') { |
| type = 'linear' |
| } else if (this.name === 'repeating-linear-gradient') { |
| type = 'repeating-linear' |
| } else if (this.name === 'repeating-radial-gradient') { |
| type = 'repeating-radial' |
| } else { |
| type = 'radial' |
| } |
| let string = '-gradient' |
| let regexp = utils.regexp( |
| `-webkit-(${type}-gradient|gradient\\(\\s*${type})`, |
| false |
| ) |
|
|
| return new OldValue(this.name, prefix + this.name, string, regexp) |
| } else { |
| return super.old(prefix) |
| } |
| } |
|
|
| |
| |
| |
| oldDirection(params) { |
| let div = this.cloneDiv(params[0]) |
|
|
| if (params[0][0].value !== 'to') { |
| return params.unshift([ |
| { type: 'word', value: Gradient.oldDirections.bottom }, |
| div |
| ]) |
| } else { |
| let words = [] |
| for (let node of params[0].slice(2)) { |
| if (node.type === 'word') { |
| words.push(node.value.toLowerCase()) |
| } |
| } |
|
|
| words = words.join(' ') |
| let old = Gradient.oldDirections[words] || words |
|
|
| params[0] = [{ type: 'word', value: old }, div] |
| return params[0] |
| } |
| } |
|
|
| |
| |
| |
| oldWebkit(node) { |
| let { nodes } = node |
| let string = parser.stringify(node.nodes) |
|
|
| if (this.name !== 'linear-gradient') { |
| return false |
| } |
| if (nodes[0] && nodes[0].value.includes('deg')) { |
| return false |
| } |
| if ( |
| string.includes('px') || |
| string.includes('-corner') || |
| string.includes('-side') |
| ) { |
| return false |
| } |
|
|
| let params = [[]] |
| for (let i of nodes) { |
| params[params.length - 1].push(i) |
| if (i.type === 'div' && i.value === ',') { |
| params.push([]) |
| } |
| } |
|
|
| this.oldDirection(params) |
| this.colorStops(params) |
|
|
| node.nodes = [] |
| for (let param of params) { |
| node.nodes = node.nodes.concat(param) |
| } |
|
|
| node.nodes.unshift( |
| { type: 'word', value: 'linear' }, |
| this.cloneDiv(node.nodes) |
| ) |
| node.value = '-webkit-gradient' |
|
|
| return true |
| } |
|
|
| |
| |
| |
| replace(string, prefix) { |
| let ast = parser(string) |
| for (let node of ast.nodes) { |
| let gradientName = this.name |
| if (node.type === 'function' && node.value === gradientName) { |
| node.nodes = this.newDirection(node.nodes) |
| node.nodes = this.normalize(node.nodes, gradientName) |
| if (prefix === '-webkit- old') { |
| let changes = this.oldWebkit(node) |
| if (!changes) { |
| return false |
| } |
| } else { |
| node.nodes = this.convertDirection(node.nodes) |
| node.value = prefix + node.value |
| } |
| } |
| } |
| return ast.toString() |
| } |
|
|
| |
| |
| |
| replaceFirst(params, ...words) { |
| let prefix = words.map(i => { |
| if (i === ' ') { |
| return { type: 'space', value: i } |
| } |
| return { type: 'word', value: i } |
| }) |
| return prefix.concat(params.slice(1)) |
| } |
|
|
| revertDirection(word) { |
| return Gradient.directions[word.toLowerCase()] || word |
| } |
|
|
| |
| |
| |
| roundFloat(float, digits) { |
| return parseFloat(float.toFixed(digits)) |
| } |
| } |
|
|
| Gradient.names = [ |
| 'linear-gradient', |
| 'repeating-linear-gradient', |
| 'radial-gradient', |
| 'repeating-radial-gradient' |
| ] |
|
|
| Gradient.directions = { |
| bottom: 'top', |
| left: 'right', |
| right: 'left', |
| top: 'bottom' |
| } |
|
|
| |
| Gradient.oldDirections = { |
| 'bottom': 'left top, left bottom', |
| 'bottom left': 'right top, left bottom', |
| 'bottom right': 'left top, right bottom', |
| 'left': 'right top, left top', |
|
|
| 'left bottom': 'right top, left bottom', |
| 'left top': 'right bottom, left top', |
| 'right': 'left top, right top', |
| 'right bottom': 'left top, right bottom', |
| 'right top': 'left bottom, right top', |
| 'top': 'left bottom, left top', |
| 'top left': 'right bottom, left top', |
| 'top right': 'left bottom, right top' |
| } |
|
|
| module.exports = Gradient |
|
|