@@ -4,6 +4,12 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
4
4
import { CssNode } from './interfaces' ;
5
5
import Component from '../Component' ;
6
6
7
+ enum BlockAppliesToNode {
8
+ NotPossible ,
9
+ Possible ,
10
+ UnknownSelectorType
11
+ }
12
+
7
13
export default class Selector {
8
14
node : CssNode ;
9
15
stylesheet : Stylesheet ;
@@ -31,10 +37,10 @@ export default class Selector {
31
37
apply ( node : CssNode , stack : CssNode [ ] ) {
32
38
const to_encapsulate : CssNode [ ] = [ ] ;
33
39
34
- apply_selector ( this . stylesheet , this . local_blocks . slice ( ) , node , stack . slice ( ) , to_encapsulate ) ;
40
+ apply_selector ( this . local_blocks . slice ( ) , node , stack . slice ( ) , to_encapsulate ) ;
35
41
36
42
if ( to_encapsulate . length > 0 ) {
37
- to_encapsulate . filter ( ( _ , i ) => i === 0 || i === to_encapsulate . length - 1 ) . forEach ( ( { node, block } ) => {
43
+ to_encapsulate . forEach ( ( { node, block } ) => {
38
44
this . stylesheet . nodes_with_css_class . add ( node ) ;
39
45
block . should_encapsulate = true ;
40
46
} ) ;
@@ -126,57 +132,38 @@ export default class Selector {
126
132
}
127
133
}
128
134
129
- function apply_selector ( stylesheet : Stylesheet , blocks : Block [ ] , node : CssNode , stack : CssNode [ ] , to_encapsulate : any [ ] ) : boolean {
135
+ function apply_selector ( blocks : Block [ ] , node : CssNode , stack : CssNode [ ] , to_encapsulate : any [ ] ) : boolean {
130
136
const block = blocks . pop ( ) ;
131
137
if ( ! block ) return false ;
132
138
133
139
if ( ! node ) {
134
140
return blocks . every ( block => block . global ) ;
135
141
}
136
142
137
- let i = block . selectors . length ;
138
-
139
- while ( i -- ) {
140
- const selector = block . selectors [ i ] ;
141
- const name = typeof selector . name === 'string' && selector . name . replace ( / \\ ( .) / g, '$1' ) ;
142
-
143
- if ( selector . type === 'PseudoClassSelector' && name === 'global' ) {
144
- // TODO shouldn't see this here... maybe we should enforce that :global(...)
145
- // cannot be sandwiched between non-global selectors?
143
+ switch ( block_might_apply_to_node ( block , node ) ) {
144
+ case BlockAppliesToNode . NotPossible :
146
145
return false ;
147
- }
148
-
149
- if ( selector . type === 'PseudoClassSelector' || selector . type === 'PseudoElementSelector' ) {
150
- continue ;
151
- }
152
-
153
- if ( selector . type === 'ClassSelector' ) {
154
- if ( ! attribute_matches ( node , 'class' , name , '~=' , false ) && ! node . classes . some ( c => c . name === name ) ) return false ;
155
- }
156
-
157
- else if ( selector . type === 'IdSelector' ) {
158
- if ( ! attribute_matches ( node , 'id' , name , '=' , false ) ) return false ;
159
- }
160
146
161
- else if ( selector . type === 'AttributeSelector' ) {
162
- if ( ! attribute_matches ( node , selector . name . name , selector . value && unquote ( selector . value ) , selector . matcher , selector . flags ) ) return false ;
163
- }
164
-
165
- else if ( selector . type === 'TypeSelector' ) {
166
- if ( node . name . toLowerCase ( ) !== name . toLowerCase ( ) && name !== '*' ) return false ;
167
- }
168
-
169
- else {
147
+ case BlockAppliesToNode . UnknownSelectorType :
170
148
// bail. TODO figure out what these could be
171
149
to_encapsulate . push ( { node, block } ) ;
172
150
return true ;
173
- }
174
151
}
175
152
176
153
if ( block . combinator ) {
177
154
if ( block . combinator . type === 'WhiteSpace' ) {
178
- while ( stack . length ) {
179
- if ( apply_selector ( stylesheet , blocks . slice ( ) , stack . pop ( ) , stack , to_encapsulate ) ) {
155
+ for ( const ancestor_block of blocks ) {
156
+ if ( ancestor_block . global ) {
157
+ continue ;
158
+ }
159
+
160
+ for ( const stack_node of stack ) {
161
+ if ( block_might_apply_to_node ( ancestor_block , stack_node ) !== BlockAppliesToNode . NotPossible ) {
162
+ to_encapsulate . push ( { node : stack_node , block : ancestor_block } ) ;
163
+ }
164
+ }
165
+
166
+ if ( to_encapsulate . length ) {
180
167
to_encapsulate . push ( { node, block } ) ;
181
168
return true ;
182
169
}
@@ -189,7 +176,7 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode,
189
176
190
177
return false ;
191
178
} else if ( block . combinator . name === '>' ) {
192
- if ( apply_selector ( stylesheet , blocks , stack . pop ( ) , stack , to_encapsulate ) ) {
179
+ if ( apply_selector ( blocks , stack . pop ( ) , stack , to_encapsulate ) ) {
193
180
to_encapsulate . push ( { node, block } ) ;
194
181
return true ;
195
182
}
@@ -206,6 +193,47 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode,
206
193
return true ;
207
194
}
208
195
196
+ function block_might_apply_to_node ( block , node ) : BlockAppliesToNode {
197
+ let i = block . selectors . length ;
198
+
199
+ while ( i -- ) {
200
+ const selector = block . selectors [ i ] ;
201
+ const name = typeof selector . name === 'string' && selector . name . replace ( / \\ ( .) / g, '$1' ) ;
202
+
203
+ if ( selector . type === 'PseudoClassSelector' || selector . type === 'PseudoElementSelector' ) {
204
+ continue ;
205
+ }
206
+
207
+ if ( selector . type === 'PseudoClassSelector' && name === 'global' ) {
208
+ // TODO shouldn't see this here... maybe we should enforce that :global(...)
209
+ // cannot be sandwiched between non-global selectors?
210
+ return BlockAppliesToNode . NotPossible ;
211
+ }
212
+
213
+ if ( selector . type === 'ClassSelector' ) {
214
+ if ( ! attribute_matches ( node , 'class' , name , '~=' , false ) && ! node . classes . some ( c => c . name === name ) ) return BlockAppliesToNode . NotPossible ;
215
+ }
216
+
217
+ else if ( selector . type === 'IdSelector' ) {
218
+ if ( ! attribute_matches ( node , 'id' , name , '=' , false ) ) return BlockAppliesToNode . NotPossible ;
219
+ }
220
+
221
+ else if ( selector . type === 'AttributeSelector' ) {
222
+ if ( ! attribute_matches ( node , selector . name . name , selector . value && unquote ( selector . value ) , selector . matcher , selector . flags ) ) return BlockAppliesToNode . NotPossible ;
223
+ }
224
+
225
+ else if ( selector . type === 'TypeSelector' ) {
226
+ if ( node . name . toLowerCase ( ) !== name . toLowerCase ( ) && name !== '*' ) return BlockAppliesToNode . NotPossible ;
227
+ }
228
+
229
+ else {
230
+ return BlockAppliesToNode . UnknownSelectorType ;
231
+ }
232
+ }
233
+
234
+ return BlockAppliesToNode . Possible ;
235
+ }
236
+
209
237
function test_attribute ( operator , expected_value , case_insensitive , value ) {
210
238
if ( case_insensitive ) {
211
239
expected_value = expected_value . toLowerCase ( ) ;
0 commit comments