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/gi
- class 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 replace
- Gradient.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
|