summaryrefslogtreecommitdiff
path: root/node_modules/autoprefixer/lib/hacks/gradient.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/autoprefixer/lib/hacks/gradient.js')
-rw-r--r--node_modules/autoprefixer/lib/hacks/gradient.js448
1 files changed, 448 insertions, 0 deletions
diff --git a/node_modules/autoprefixer/lib/hacks/gradient.js b/node_modules/autoprefixer/lib/hacks/gradient.js
new file mode 100644
index 0000000..f2345b0
--- /dev/null
+++ b/node_modules/autoprefixer/lib/hacks/gradient.js
@@ -0,0 +1,448 @@
+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 {
+ /**
+ * Do not add non-webkit prefixes for list-style and object
+ */
+ 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
+ }
+
+ /**
+ * Get div token from exists parameters
+ */
+ cloneDiv(params) {
+ for (let i of params) {
+ if (i.type === 'div' && i.value === ',') {
+ return i
+ }
+ }
+ return { after: ' ', type: 'div', value: ',' }
+ }
+
+ /**
+ * Change colors syntax to old webkit
+ */
+ 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
+ }
+
+ /**
+ * Change new direction to old
+ */
+ 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
+ }
+
+ /**
+ * Add 90 degrees
+ */
+ 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`
+ }
+
+ /**
+ * Replace `to top left` to `bottom right`
+ */
+ 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)
+ }
+ }
+ }
+
+ /**
+ * Fix radial direction syntax
+ */
+ 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)
+ }
+
+ /**
+ * Look for at word
+ */
+ 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
+ }
+
+ /**
+ * Replace old direction to new
+ */
+ newDirection(params) {
+ if (params[0].value === 'to') {
+ return params
+ }
+ IS_DIRECTION.lastIndex = 0 // reset search index of global regexp
+ 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 angle
+ */
+ 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
+
+ // Unitless zero for `<angle>` values are allowed in CSS gradients and transforms.
+ // Spec: https://github.com/w3c/csswg-drafts/commit/602789171429b2231223ab1e5acf8f7f11652eb3
+ 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') // default value
+ } else if (direction === '270deg') {
+ nodes = this.replaceFirst(nodes, 'to', ' ', 'left')
+ }
+ }
+
+ return nodes
+ }
+
+ /**
+ * Convert angle unit to deg
+ */
+ normalizeUnit(str, full) {
+ let num = parseFloat(str)
+ let deg = (num / full) * 360
+ return `${deg}deg`
+ }
+
+ /**
+ * Remove old WebKit gradient too
+ */
+ 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)
+ }
+ }
+
+ /**
+ * Change direction syntax to old webkit
+ */
+ 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]
+ }
+ }
+
+ /**
+ * Convert to old webkit syntax
+ */
+ 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
+ }
+
+ /**
+ * Change degrees for webkit prefix
+ */
+ replace(string, prefix) {
+ let ast = parser(string)
+ for (let node of ast.nodes) {
+ let gradientName = this.name // gradient 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()
+ }
+
+ /**
+ * Replace first token
+ */
+ 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
+ }
+
+ /**
+ * Round float and save digits under dot
+ */
+ 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' // default value
+}
+
+// Direction to replace
+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