| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 | 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/giclass Gradient extends Value {  /**   * 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))  }  /**   * Convert angle unit to deg   */  normalizeUnit(str, full) {    let num = parseFloat(str)    let deg = (num / full) * 360    return `${deg}deg`  }  /**   * 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  }  /**   * 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  }  /**   * 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  }  /**   * 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  }  /**   * 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)      }    }  }  /**   * 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`  }  /**   * 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)  }  revertDirection(word) {    return Gradient.directions[word.toLowerCase()] || word  }  /**   * Round float and save digits under dot   */  roundFloat(float, digits) {    return parseFloat(float.toFixed(digits))  }  /**   * 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 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]    }  }  /**   * Get div token from exists parameters   */  cloneDiv(params) {    for (let i of params) {      if (i.type === 'div' && i.value === ',') {        return i      }    }    return { type: 'div', value: ',', after: ' ' }  }  /**   * 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  }  /**   * 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)    }  }  /**   * 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  }}Gradient.names = [  'linear-gradient',  'repeating-linear-gradient',  'radial-gradient',  'repeating-radial-gradient']Gradient.directions = {  top: 'bottom', // default value  left: 'right',  bottom: 'top',  right: 'left'}// Direction to replaceGradient.oldDirections = {  'top': 'left bottom, left top',  'left': 'right top, left top',  'bottom': 'left top, left bottom',  'right': 'left top, right top',  'top right': 'left bottom, right top',  'top left': 'right bottom, left top',  'right top': 'left bottom, right top',  'right bottom': 'left top, right bottom',  'bottom right': 'left top, right bottom',  'bottom left': 'right top, left bottom',  'left top': 'right bottom, left top',  'left bottom': 'right top, left bottom'}module.exports = Gradient
 |