1 | let { list } = require('postcss')
|
2 | let parser = require('postcss-value-parser')
|
3 |
|
4 | let Browsers = require('./browsers')
|
5 | let vendor = require('./vendor')
|
6 |
|
7 | class Transition {
|
8 | constructor(prefixes) {
|
9 | this.props = ['transition', 'transition-property']
|
10 | this.prefixes = prefixes
|
11 | }
|
12 |
|
13 | |
14 |
|
15 |
|
16 | add(decl, result) {
|
17 | let prefix, prop
|
18 | 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 | return
|
28 | }
|
29 |
|
30 | for (let param of params) {
|
31 | prop = this.findProp(param)
|
32 | if (prop[0] === '-') continue
|
33 |
|
34 | let prefixer = this.prefixes.add[prop]
|
35 | if (!prefixer || !prefixer.prefixes) continue
|
36 |
|
37 | for (prefix of prefixer.prefixes) {
|
38 | if (vendorPrefixes && !vendorPrefixes.some(p => prefix.includes(p))) {
|
39 | continue
|
40 | }
|
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 = value
|
79 | }
|
80 | }
|
81 |
|
82 | |
83 |
|
84 |
|
85 | findProp(param) {
|
86 | let prop = param[0].value
|
87 | if (/^\d/.test(prop)) {
|
88 | for (let [i, token] of param.entries()) {
|
89 | if (i !== 0 && token.type === 'word') {
|
90 | return token.value
|
91 | }
|
92 | }
|
93 | }
|
94 | return prop
|
95 | }
|
96 |
|
97 | |
98 |
|
99 |
|
100 | already(decl, prop, value) {
|
101 | return decl.parent.some(i => i.prop === prop && i.value === value)
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 | cloneBefore(decl, prop, value) {
|
108 | if (!this.already(decl, prop, value)) {
|
109 | decl.cloneBefore({ prop, value })
|
110 | }
|
111 | }
|
112 |
|
113 | |
114 |
|
115 |
|
116 | checkForWarning(result, decl) {
|
117 | if (decl.prop !== 'transition-property') {
|
118 | return
|
119 | }
|
120 |
|
121 | let isPrefixed = false
|
122 | let hasAssociatedProp = false
|
123 |
|
124 | decl.parent.each(i => {
|
125 | if (i.type !== 'decl') {
|
126 | return undefined
|
127 | }
|
128 | if (i.prop.indexOf('transition-') !== 0) {
|
129 | return undefined
|
130 | }
|
131 | let values = list.comma(i.value)
|
132 |
|
133 | if (i.prop === 'transition-property') {
|
134 | values.forEach(value => {
|
135 | let lookup = this.prefixes.add[value]
|
136 | if (lookup && lookup.prefixes && lookup.prefixes.length > 0) {
|
137 | isPrefixed = true
|
138 | }
|
139 | })
|
140 | return undefined
|
141 | }
|
142 |
|
143 | hasAssociatedProp = hasAssociatedProp || values.length > 1
|
144 | return false
|
145 | })
|
146 |
|
147 | if (isPrefixed && hasAssociatedProp) {
|
148 | decl.warn(
|
149 | result,
|
150 | 'Replace transition-property to transition, ' +
|
151 | 'because Autoprefixer could not support ' +
|
152 | 'any cases of transition-property ' +
|
153 | 'and other transition-*'
|
154 | )
|
155 | }
|
156 | }
|
157 |
|
158 | |
159 |
|
160 |
|
161 | remove(decl) {
|
162 | let params = this.parse(decl.value)
|
163 | params = params.filter(i => {
|
164 | let prop = this.prefixes.remove[this.findProp(i)]
|
165 | return !prop || !prop.remove
|
166 | })
|
167 | let value = this.stringify(params)
|
168 |
|
169 | if (decl.value === value) {
|
170 | return
|
171 | }
|
172 |
|
173 | if (params.length === 0) {
|
174 | decl.remove()
|
175 | return
|
176 | }
|
177 |
|
178 | let double = decl.parent.some(i => {
|
179 | return i.prop === decl.prop && i.value === value
|
180 | })
|
181 | let smaller = decl.parent.some(i => {
|
182 | return i !== decl && i.prop === decl.prop && i.value.length > value.length
|
183 | })
|
184 |
|
185 | if (double || smaller) {
|
186 | decl.remove()
|
187 | return
|
188 | }
|
189 |
|
190 | decl.value = value
|
191 | }
|
192 |
|
193 | |
194 |
|
195 |
|
196 | parse(value) {
|
197 | let ast = parser(value)
|
198 | let result = []
|
199 | let param = []
|
200 | for (let node of ast.nodes) {
|
201 | param.push(node)
|
202 | if (node.type === 'div' && node.value === ',') {
|
203 | result.push(param)
|
204 | param = []
|
205 | }
|
206 | }
|
207 | result.push(param)
|
208 | return result.filter(i => i.length > 0)
|
209 | }
|
210 |
|
211 | |
212 |
|
213 |
|
214 | stringify(params) {
|
215 | if (params.length === 0) {
|
216 | return ''
|
217 | }
|
218 | let nodes = []
|
219 | for (let param of params) {
|
220 | if (param[param.length - 1].type !== 'div') {
|
221 | param.push(this.div(params))
|
222 | }
|
223 | nodes = nodes.concat(param)
|
224 | }
|
225 | if (nodes[0].type === 'div') {
|
226 | nodes = nodes.slice(1)
|
227 | }
|
228 | if (nodes[nodes.length - 1].type === 'div') {
|
229 | nodes = nodes.slice(0, +-2 + 1 || undefined)
|
230 | }
|
231 | return parser.stringify({ nodes })
|
232 | }
|
233 |
|
234 | |
235 |
|
236 |
|
237 | clone(origin, name, param) {
|
238 | let result = []
|
239 | let changed = false
|
240 | for (let i of param) {
|
241 | if (!changed && i.type === 'word' && i.value === origin) {
|
242 | result.push({ type: 'word', value: name })
|
243 | changed = true
|
244 | } else {
|
245 | result.push(i)
|
246 | }
|
247 | }
|
248 | return result
|
249 | }
|
250 |
|
251 | |
252 |
|
253 |
|
254 | div(params) {
|
255 | for (let param of params) {
|
256 | for (let node of param) {
|
257 | if (node.type === 'div' && node.value === ',') {
|
258 | return node
|
259 | }
|
260 | }
|
261 | }
|
262 | return { type: 'div', value: ',', after: ' ' }
|
263 | }
|
264 |
|
265 | cleanOtherPrefixes(params, prefix) {
|
266 | return params.filter(param => {
|
267 | let current = vendor.prefix(this.findProp(param))
|
268 | return current === '' || current === prefix
|
269 | })
|
270 | }
|
271 |
|
272 | |
273 |
|
274 |
|
275 | cleanFromUnprefixed(params, prefix) {
|
276 | let remove = params
|
277 | .map(i => this.findProp(i))
|
278 | .filter(i => i.slice(0, prefix.length) === prefix)
|
279 | .map(i => this.prefixes.unprefixed(i))
|
280 |
|
281 | let result = []
|
282 | for (let param of params) {
|
283 | let prop = this.findProp(param)
|
284 | let p = vendor.prefix(prop)
|
285 | if (!remove.includes(prop) && (p === prefix || p === '')) {
|
286 | result.push(param)
|
287 | }
|
288 | }
|
289 | return result
|
290 | }
|
291 |
|
292 | |
293 |
|
294 |
|
295 | disabled(prop, prefix) {
|
296 | let other = ['order', 'justify-content', 'align-self', 'align-content']
|
297 | if (prop.includes('flex') || other.includes(prop)) {
|
298 | if (this.prefixes.options.flexbox === false) {
|
299 | return true
|
300 | }
|
301 |
|
302 | if (this.prefixes.options.flexbox === 'no-2009') {
|
303 | return prefix.includes('2009')
|
304 | }
|
305 | }
|
306 | return undefined
|
307 | }
|
308 |
|
309 | |
310 |
|
311 |
|
312 | ruleVendorPrefixes(decl) {
|
313 | let { parent } = decl
|
314 |
|
315 | if (parent.type !== 'rule') {
|
316 | return false
|
317 | } else if (!parent.selector.includes(':-')) {
|
318 | return false
|
319 | }
|
320 |
|
321 | let selectors = Browsers.prefixes().filter(s =>
|
322 | parent.selector.includes(':' + s)
|
323 | )
|
324 |
|
325 | return selectors.length > 0 ? selectors : false
|
326 | }
|
327 | }
|
328 |
|
329 | module.exports = Transition
|