AK-21/Graphite-Industrial-Intelligence
0
1let { list } = require('postcss')2let parser = require('postcss-value-parser')3 4let Browsers = require('./browsers')5let vendor = require('./vendor')6 7class Transition {8 constructor(prefixes) {9 this.props = ['transition', 'transition-property']10 this.prefixes = prefixes11 }12 13 /**14 * Process transition and add prefixes for all necessary properties15 */16 add(decl, result) {17 let prefix, prop18 let add = this.prefixes.add[decl.prop]19 let vendorPrefixes = this.ruleVendorPrefixes(decl)20 let declPrefixes = vendorPrefixes || (add && add.prefixes) || []21 22 let params = this.parse(decl.value)23 let names = params.map(i => this.findProp(i))24 let added = []25 26 if (names.some(i => i[0] === '-')) {27 return28 }29 30 for (let param of params) {31 prop = this.findProp(param)32 if (prop[0] === '-') continue33 34 let prefixer = this.prefixes.add[prop]35 if (!prefixer || !prefixer.prefixes) continue36 37 for (prefix of prefixer.prefixes) {38 if (vendorPrefixes && !vendorPrefixes.some(p => prefix.includes(p))) {39 continue40 }41 42 let prefixed = this.prefixes.prefixed(prop, prefix)43 if (prefixed !== '-ms-transform' && !names.includes(prefixed)) {44 if (!this.disabled(prop, prefix)) {45 added.push(this.clone(prop, prefixed, param))46 }47 }48 }49 }50 51 params = params.concat(added)52 let value = this.stringify(params)53 54 let webkitClean = this.stringify(55 this.cleanFromUnprefixed(params, '-webkit-')56 )57 if (declPrefixes.includes('-webkit-')) {58 this.cloneBefore(decl, `-webkit-${decl.prop}`, webkitClean)59 }60 this.cloneBefore(decl, decl.prop, webkitClean)61 if (declPrefixes.includes('-o-')) {62 let operaClean = this.stringify(this.cleanFromUnprefixed(params, '-o-'))63 this.cloneBefore(decl, `-o-${decl.prop}`, operaClean)64 }65 66 for (prefix of declPrefixes) {67 if (prefix !== '-webkit-' && prefix !== '-o-') {68 let prefixValue = this.stringify(69 this.cleanOtherPrefixes(params, prefix)70 )71 this.cloneBefore(decl, prefix + decl.prop, prefixValue)72 }73 }74 75 if (value !== decl.value && !this.already(decl, decl.prop, value)) {76 this.checkForWarning(result, decl)77 decl.cloneBefore()78 decl.value = value79 }80 }81 82 /**83 * Does we already have this declaration84 */85 already(decl, prop, value) {86 return decl.parent.some(i => i.prop === prop && i.value === value)87 }88 89 /**90 * Show transition-property warning91 */92 checkForWarning(result, decl) {93 if (decl.prop !== 'transition-property') {94 return95 }96 97 let isPrefixed = false98 let hasAssociatedProp = false99 100 decl.parent.each(i => {101 if (i.type !== 'decl') {102 return undefined103 }104 if (i.prop.indexOf('transition-') !== 0) {105 return undefined106 }107 let values = list.comma(i.value)108 // check if current Rule's transition-property comma separated value list needs prefixes109 if (i.prop === 'transition-property') {110 values.forEach(value => {111 let lookup = this.prefixes.add[value]112 if (lookup && lookup.prefixes && lookup.prefixes.length > 0) {113 isPrefixed = true114 }115 })116 return undefined117 }118 // check if another transition-* prop in current Rule has comma separated value list119 hasAssociatedProp = hasAssociatedProp || values.length > 1120 return false121 })122 123 if (isPrefixed && hasAssociatedProp) {124 decl.warn(125 result,126 'Replace transition-property to transition, ' +127 'because Autoprefixer could not support ' +128 'any cases of transition-property ' +129 'and other transition-*'130 )131 }132 }133 134 /**135 * Remove all non-webkit prefixes and unprefixed params if we have prefixed136 */137 cleanFromUnprefixed(params, prefix) {138 let remove = params139 .map(i => this.findProp(i))140 .filter(i => i.slice(0, prefix.length) === prefix)141 .map(i => this.prefixes.unprefixed(i))142 143 let result = []144 for (let param of params) {145 let prop = this.findProp(param)146 let p = vendor.prefix(prop)147 if (!remove.includes(prop) && (p === prefix || p === '')) {148 result.push(param)149 }150 }151 return result152 }153 154 cleanOtherPrefixes(params, prefix) {155 return params.filter(param => {156 let current = vendor.prefix(this.findProp(param))157 return current === '' || current === prefix158 })159 }160 161 /**162 * Return new param array with different name163 */164 clone(origin, name, param) {165 let result = []166 let changed = false167 for (let i of param) {168 if (!changed && i.type === 'word' && i.value === origin) {169 result.push({ type: 'word', value: name })170 changed = true171 } else {172 result.push(i)173 }174 }175 return result176 }177 178 /**179 * Add declaration if it is not exist180 */181 cloneBefore(decl, prop, value) {182 if (!this.already(decl, prop, value)) {183 decl.cloneBefore({ prop, value })184 }185 }186 187 /**188 * Check property for disabled by option189 */190 disabled(prop, prefix) {191 let other = ['order', 'justify-content', 'align-self', 'align-content']192 if (prop.includes('flex') || other.includes(prop)) {193 if (this.prefixes.options.flexbox === false) {194 return true195 }196 197 if (this.prefixes.options.flexbox === 'no-2009') {198 return prefix.includes('2009')199 }200 }201 return undefined202 }203 204 /**205 * Find or create separator206 */207 div(params) {208 for (let param of params) {209 for (let node of param) {210 if (node.type === 'div' && node.value === ',') {211 return node212 }213 }214 }215 return { after: ' ', type: 'div', value: ',' }216 }217 218 /**219 * Find property name220 */221 findProp(param) {222 let prop = param[0].value223 if (/^\d/.test(prop)) {224 for (let [i, token] of param.entries()) {225 if (i !== 0 && token.type === 'word') {226 return token.value227 }228 }229 }230 return prop231 }232 233 /**234 * Parse properties list to array235 */236 parse(value) {237 let ast = parser(value)238 let result = []239 let param = []240 for (let node of ast.nodes) {241 param.push(node)242 if (node.type === 'div' && node.value === ',') {243 result.push(param)244 param = []245 }246 }247 result.push(param)248 return result.filter(i => i.length > 0)249 }250 251 /**252 * Process transition and remove all unnecessary properties253 */254 remove(decl) {255 let params = this.parse(decl.value)256 params = params.filter(i => {257 let prop = this.prefixes.remove[this.findProp(i)]258 return !prop || !prop.remove259 })260 let value = this.stringify(params)261 262 if (decl.value === value) {263 return264 }265 266 if (params.length === 0) {267 decl.remove()268 return269 }270 271 let double = decl.parent.some(i => {272 return i.prop === decl.prop && i.value === value273 })274 let smaller = decl.parent.some(i => {275 return i !== decl && i.prop === decl.prop && i.value.length > value.length276 })277 278 if (double || smaller) {279 decl.remove()280 return281 }282 283 decl.value = value284 }285 286 /**287 * Check if transition prop is inside vendor specific rule288 */289 ruleVendorPrefixes(decl) {290 let { parent } = decl291 292 if (parent.type !== 'rule') {293 return false294 } else if (!parent.selector.includes(':-')) {295 return false296 }297 298 let selectors = Browsers.prefixes().filter(s =>299 parent.selector.includes(':' + s)300 )301 302 return selectors.length > 0 ? selectors : false303 }304 305 /**306 * Return properties string from array307 */308 stringify(params) {309 if (params.length === 0) {310 return ''311 }312 let nodes = []313 for (let param of params) {314 if (param[param.length - 1].type !== 'div') {315 param.push(this.div(params))316 }317 nodes.push(...param)318 }319 if (nodes[0].type === 'div') {320 nodes = nodes.slice(1)321 }322 if (nodes[nodes.length - 1].type === 'div') {323 nodes = nodes.slice(0, -1)324 }325 return parser.stringify({ nodes })326 }327}328 329module.exports = Transition330 