1
+ import { Dictionary } from "async" ;
1
2
import { postcss } from "opticss" ;
2
3
3
- import { AttributeSelector , BlockExport , BlockReference , BlockSyntaxVersion , ClassSelector , Declaration , DefinitionAST , DefinitionRoot , ForeignAttributeSelector , GlobalDeclaration , LocalBlockExport , Mapper , Name , Rename , Rule , ScopeSelector , Selector , Visitor , builders , map as mapToDefinition , visit } from "../BlockParser/ast" ;
4
+ import { AttributeSelector , BlockExport , BlockReference , BlockSyntaxVersion , ClassSelector , Declaration , DefinitionAST , DefinitionRoot , ForeignAttributeSelector , GlobalDeclaration , KeyCompoundSelector , LocalBlockExport , Mapper , Name , Rename , Rule , ScopeSelector , Selector , Visitor , builders , map as mapToDefinition , visit } from "../BlockParser/ast" ;
4
5
import { BLOCK_GLOBAL } from "../BlockSyntax" ;
5
- import { Block , BlockClass , Style , isAttrValue , isBlockClass } from "../BlockTree" ;
6
+ import { AttrValue , Block , BlockClass , Style , isAttrValue , isBlockClass } from "../BlockTree" ;
6
7
import { ResolvedConfiguration } from "../configuration" ;
7
8
8
9
export const INLINE_DEFINITION_FILE = Symbol ( "Inline Definition" ) ;
@@ -191,34 +192,35 @@ export class BlockDefinitionCompiler {
191
192
for ( let style of block . all ( true ) ) {
192
193
definitionRoot . children . push ( this . styleToRule ( style , reservedClassNames ) ) ;
193
194
}
195
+ definitionRoot . children . push ( ...this . complexCompositions ( block ) ) ;
194
196
return definitionRoot ;
195
197
}
196
198
197
199
styleToRule ( style : Style , reservedClassNames : Set < string > ) : Rule < DefinitionAST > {
198
200
let selectors = new Array < Selector < DefinitionAST > > ( ) ;
199
201
let blockClass : BlockClass = isAttrValue ( style ) ? style . blockClass : style ;
200
- let elementSelector : ClassSelector | ScopeSelector ;
201
- if ( blockClass . isRoot ) {
202
- elementSelector = builders . scopeSelector ( ) ;
203
- } else {
204
- elementSelector = builders . classSelector ( blockClass . name ) ;
205
- }
206
202
if ( isAttrValue ( style ) ) {
207
- let attributeSelector : AttributeSelector ;
208
- if ( style . isPresenceRule ) {
209
- attributeSelector = builders . attributeSelector ( style . attribute . name ) ;
210
- } else {
211
- attributeSelector = builders . attributeSelector ( style . attribute . name , style . value ) ;
212
- }
213
- selectors . push ( builders . keyCompoundSelector ( elementSelector , [ attributeSelector ] ) ) ;
203
+ selectors . push ( attributeSelectors ( blockClass , [ style ] ) ) ;
214
204
} else {
215
- selectors . push ( elementSelector ) ;
205
+ selectors . push ( elementSelector ( blockClass ) ) ;
216
206
}
217
207
let declarations = new Array < Declaration > ( ) ;
218
208
if ( isBlockClass ( style ) && style . isRoot ) {
219
209
declarations . push ( builders . declaration ( "block-id" , `"${ style . block . guid } "` ) ) ;
220
210
declarations . push ( builders . declaration ( "block-name" , `"${ style . block . name } "` ) ) ;
221
211
}
212
+
213
+ let compositions = new Array < string > ( ) ;
214
+ for ( let composition of blockClass . composedStyles ( ) ) {
215
+ if ( composition . conditions . length === 0 && blockClass === style ) {
216
+ compositions . push ( composition . path ) ;
217
+ } else if ( composition . conditions . length === 1 && composition . conditions [ 0 ] === style ) {
218
+ compositions . push ( composition . path ) ;
219
+ }
220
+ }
221
+ if ( compositions . length > 0 ) {
222
+ declarations . push ( builders . declaration ( "composes" , compositions . join ( ", " ) ) ) ;
223
+ }
222
224
declarations . push ( builders . declaration ( "block-class" , style . cssClass ( this . config , reservedClassNames ) ) ) ;
223
225
declarations . push ( builders . declaration ( "block-interface-index" , style . index . toString ( ) ) ) ;
224
226
let aliasValues = new Array ( ...style . getStyleAliases ( ) ) ;
@@ -227,6 +229,48 @@ export class BlockDefinitionCompiler {
227
229
}
228
230
return builders . rule ( selectors , declarations ) ;
229
231
}
232
+ /**
233
+ * Simple compositions (which apply to a single block class or attribute) are
234
+ * processed when we generate the rule for that style. The complex
235
+ * compositions which apply to the intersection of more than one attribute
236
+ * require the generation of ruleset that targets all of those attributes
237
+ * together.
238
+ */
239
+ complexCompositions ( block : Block ) : Array < Rule < DefinitionAST > > {
240
+ let complexCompositions : Dictionary < { blockClass : BlockClass ; attributes : AttrValue [ ] ; paths : Array < string > } > = { } ;
241
+ for ( let blockClass of block . classes ) {
242
+ // Compositions can have any number of attributes and we need to collate the
243
+ // styles being composed for each unique set of attributes. To do this, we
244
+ // generate a unique key for each unique set of attributes and store the data
245
+ // we need against it.
246
+ for ( let composition of blockClass . composedStyles ( ) ) {
247
+ if ( composition . conditions . length > 1 ) {
248
+ let key = composition . conditions . map ( c => c . index ) . sort ( ) . join ( " " ) ;
249
+ if ( ! complexCompositions [ key ] ) {
250
+ complexCompositions [ key ] = {
251
+ blockClass,
252
+ attributes : composition . conditions ,
253
+ paths : [ composition . path ] ,
254
+ } ;
255
+ } else {
256
+ complexCompositions [ key ] . paths . push ( composition . path ) ;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ // once we've collated all the compositions by the attributes we generate
262
+ // a rule for each distinct set of attributes and put a composes declaration
263
+ // in it.
264
+ let rules = new Array < Rule < DefinitionAST > > ( ) ;
265
+ for ( let key of Object . keys ( complexCompositions ) ) {
266
+ let composition = complexCompositions [ key ] ;
267
+ let selector = attributeSelectors ( composition . blockClass , composition . attributes ) ;
268
+ let declarations = new Array < Declaration > ( ) ;
269
+ declarations . push ( builders . declaration ( "composes" , composition . paths . join ( ", " ) ) ) ;
270
+ rules . push ( builders . rule ( [ selector ] , declarations ) ) ;
271
+ }
272
+ return rules ;
273
+ }
230
274
231
275
blockReferences ( root : postcss . Root , block : Block ) : void {
232
276
block . eachBlockReference ( ( name , _block ) => {
@@ -244,3 +288,23 @@ export class BlockDefinitionCompiler {
244
288
throw new Error ( "Method not implemented." ) ;
245
289
}
246
290
}
291
+
292
+ function elementSelector ( blockClass : BlockClass ) : ClassSelector | ScopeSelector {
293
+ if ( blockClass . isRoot ) {
294
+ return builders . scopeSelector ( ) ;
295
+ } else {
296
+ return builders . classSelector ( blockClass . name ) ;
297
+ }
298
+ }
299
+
300
+ function attributeSelectors ( blockClass : BlockClass , attributes : Array < AttrValue > ) : KeyCompoundSelector < DefinitionAST > {
301
+ let attributeSelectors = new Array < AttributeSelector > ( ) ;
302
+ for ( let style of attributes ) {
303
+ if ( style . isPresenceRule ) {
304
+ attributeSelectors . push ( builders . attributeSelector ( style . attribute . name ) ) ;
305
+ } else {
306
+ attributeSelectors . push ( builders . attributeSelector ( style . attribute . name , style . value ) ) ;
307
+ }
308
+ }
309
+ return builders . keyCompoundSelector ( elementSelector ( blockClass ) , attributeSelectors ) ;
310
+ }
0 commit comments