1
1
/* @flow */
2
2
import prefixAll from 'inline-style-prefixer/static' ;
3
3
4
+ import OrderedElements from './ordered-elements' ;
4
5
import {
5
6
objectToPairs , kebabifyStyleName , recursiveMerge , stringifyValue ,
6
7
importantify , flatten
@@ -143,18 +144,21 @@ export const generateCSS = (
143
144
stringHandlers /* : StringHandlers */ ,
144
145
useImportant /* : boolean */
145
146
) /* : string */ => {
146
- const merged = styleTypes . reduce ( recursiveMerge ) ;
147
+ const merged /* : OrderedElements */ = styleTypes . reduce (
148
+ recursiveMerge ,
149
+ new OrderedElements ( ) ) ;
147
150
148
- const plainDeclarations = { } ;
151
+ const plainDeclarations = new OrderedElements ( ) ;
149
152
let generatedStyles = "" ;
150
153
151
- Object . keys ( merged ) . forEach ( key => {
154
+ // TODO(emily): benchmark this to see if a plain for loop would be faster.
155
+ merged . forEach ( ( key , val ) => {
152
156
// For each key, see if one of the selector handlers will handle these
153
157
// styles.
154
158
const foundHandler = selectorHandlers . some ( handler => {
155
159
const result = handler ( key , selector , ( newSelector ) => {
156
160
return generateCSS (
157
- newSelector , [ merged [ key ] ] , selectorHandlers ,
161
+ newSelector , [ val ] , selectorHandlers ,
158
162
stringHandlers , useImportant ) ;
159
163
} ) ;
160
164
if ( result != null ) {
@@ -167,7 +171,7 @@ export const generateCSS = (
167
171
// If none of the handlers handled it, add it to the list of plain
168
172
// style declarations.
169
173
if ( ! foundHandler ) {
170
- plainDeclarations [ key ] = merged [ key ] ;
174
+ plainDeclarations . set ( key , val ) ;
171
175
}
172
176
} ) ;
173
177
@@ -186,31 +190,25 @@ export const generateCSS = (
186
190
* See generateCSSRuleset for usage and documentation of paramater types.
187
191
*/
188
192
const runStringHandlers = (
189
- declarations /* : SheetDefinition */ ,
193
+ declarations /* : OrderedElements */ ,
190
194
stringHandlers /* : StringHandlers */ ,
191
195
selectorHandlers /* : SelectorHandler[] */
192
196
) /* */ => {
193
- const result = { } ;
194
-
195
197
const hasStringHandlers = ! ! stringHandlers ;
196
- const keys = Object . keys ( declarations ) ;
197
- for ( let i = 0 ; i < keys . length ; i += 1 ) {
198
+ return declarations . map ( ( key , val ) => {
198
199
// If a handler exists for this particular key, let it interpret
199
200
// that value first before continuing
200
- if ( hasStringHandlers && stringHandlers . hasOwnProperty ( keys [ i ] ) ) {
201
+ if ( hasStringHandlers && stringHandlers . hasOwnProperty ( key ) ) {
201
202
// TODO(emily): Pass in a callback which generates CSS, similar to
202
203
// how our selector handlers work, instead of passing in
203
204
// `selectorHandlers` and have them make calls to `generateCSS`
204
205
// themselves. Right now, this is impractical because our string
205
206
// handlers are very specialized and do complex things.
206
- result [ keys [ i ] ] = stringHandlers [ keys [ i ] ] (
207
- declarations [ keys [ i ] ] , selectorHandlers ) ;
207
+ return stringHandlers [ key ] ( val , selectorHandlers ) ;
208
208
} else {
209
- result [ keys [ i ] ] = declarations [ keys [ i ] ] ;
209
+ return val ;
210
210
}
211
- }
212
-
213
- return result ;
211
+ } ) ;
214
212
} ;
215
213
216
214
/**
@@ -246,44 +244,55 @@ const runStringHandlers = (
246
244
*/
247
245
export const generateCSSRuleset = (
248
246
selector /* : string */ ,
249
- declarations /* : SheetDefinition */ ,
247
+ declarations /* : OrderedElements */ ,
250
248
stringHandlers /* : StringHandlers */ ,
251
249
useImportant /* : boolean */ ,
252
250
selectorHandlers /* : SelectorHandler[] */
253
251
) /* : string */ => {
254
- const handledDeclarations = runStringHandlers (
252
+ const handledDeclarations /* : OrderedElements */ = runStringHandlers (
255
253
declarations , stringHandlers , selectorHandlers ) ;
256
254
257
- const prefixedDeclarations = prefixAll ( handledDeclarations ) ;
255
+ const prefixedDeclarations = prefixAll ( handledDeclarations . elements ) ;
258
256
259
257
const prefixedRules = flatten (
260
258
objectToPairs ( prefixedDeclarations ) . map ( ( [ key , value ] ) => {
261
259
if ( Array . isArray ( value ) ) {
262
- // inline-style-prefix-all returns an array when there should be
263
- // multiple rules, we will flatten to single rules
264
-
265
- const prefixedValues = [ ] ;
266
- const unprefixedValues = [ ] ;
267
-
268
- value . forEach ( v => {
269
- if ( v [ 0 ] === '-' ) {
270
- prefixedValues . push ( v ) ;
271
- } else {
272
- unprefixedValues . push ( v ) ;
273
- }
274
- } ) ;
275
-
276
- prefixedValues . sort ( ) ;
277
- unprefixedValues . sort ( ) ;
278
-
279
- return prefixedValues
280
- . concat ( unprefixedValues )
281
- . map ( v => [ key , v ] ) ;
260
+ // inline-style-prefixer returns an array when there should be
261
+ // multiple rules for the same key. Here we flatten to multiple
262
+ // pairs with the same key.
263
+ return value . map ( v => [ key , v ] ) ;
282
264
}
283
265
return [ [ key , value ] ] ;
284
266
} )
285
267
) ;
286
268
269
+ // Calculate the order that we want to each element in `prefixedRules` to
270
+ // be in, based on its index in the original key ordering.
271
+ const sortOrder = { } ;
272
+ for ( let i = 0 ; i < handledDeclarations . keyOrder . length ; i ++ ) {
273
+ const key = handledDeclarations . keyOrder [ i ] ;
274
+ sortOrder [ key ] = i ;
275
+
276
+ // In order to keep most prefixed versions of keys in about the same
277
+ // order that the original keys were in but placed before the
278
+ // unprefixed version, we generate the prefixed forms of the keys and
279
+ // set their order to the same as the original key minus a little bit.
280
+ const capitalizedKey = `${ key [ 0 ] . toUpperCase ( ) } ${ key . slice ( 1 ) } ` ;
281
+ sortOrder [ `Webkit${ capitalizedKey } ` ] = i - 0.5 ;
282
+ sortOrder [ `Moz${ capitalizedKey } ` ] = i - 0.5 ;
283
+ sortOrder [ `ms${ capitalizedKey } ` ] = i - 0.5 ;
284
+ }
285
+
286
+ // Sort the prefixed rules according to the order that the keys were
287
+ // in originally before we prefixed them. New, prefixed versions
288
+ // of the rules aren't in the original list, so we set their order to -1 so
289
+ // they sort to the top.
290
+ prefixedRules . sort ( ( a , b ) => {
291
+ const aOrder = sortOrder . hasOwnProperty ( a [ 0 ] ) ? sortOrder [ a [ 0 ] ] : - 1 ;
292
+ const bOrder = sortOrder . hasOwnProperty ( b [ 0 ] ) ? sortOrder [ b [ 0 ] ] : - 1 ;
293
+ return aOrder - bOrder ;
294
+ } ) ;
295
+
287
296
const transformValue = ( useImportant === false )
288
297
? stringifyValue
289
298
: ( key , value ) => importantify ( stringifyValue ( key , value ) ) ;
0 commit comments