@@ -4,12 +4,24 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values';
4
4
import { CssNode } from './interfaces' ;
5
5
import Component from '../Component' ;
6
6
import Element from '../nodes/Element' ;
7
+ import { INode } from '../nodes/interfaces' ;
8
+ import EachBlock from '../nodes/EachBlock' ;
9
+ import IfBlock from '../nodes/IfBlock' ;
10
+ import AwaitBlock from '../nodes/AwaitBlock' ;
7
11
8
12
enum BlockAppliesToNode {
9
13
NotPossible ,
10
14
Possible ,
11
15
UnknownSelectorType
12
16
}
17
+ enum NodeExist {
18
+ Probably ,
19
+ Definitely ,
20
+ }
21
+ interface ElementAndExist {
22
+ element : Element ;
23
+ exist : NodeExist ;
24
+ }
13
25
14
26
const whitelist_attribute_selector = new Map ( [
15
27
[ 'details' , new Set ( [ 'open' ] ) ]
@@ -39,10 +51,10 @@ export default class Selector {
39
51
this . used = this . local_blocks . length === 0 ;
40
52
}
41
53
42
- apply ( node : Element , stack : Element [ ] ) {
54
+ apply ( node : Element ) {
43
55
const to_encapsulate : any [ ] = [ ] ;
44
56
45
- apply_selector ( this . local_blocks . slice ( ) , node , stack . slice ( ) , to_encapsulate ) ;
57
+ apply_selector ( this . local_blocks . slice ( ) , node , to_encapsulate ) ;
46
58
47
59
if ( to_encapsulate . length > 0 ) {
48
60
to_encapsulate . forEach ( ( { node, block } ) => {
@@ -149,7 +161,7 @@ export default class Selector {
149
161
}
150
162
}
151
163
152
- function apply_selector ( blocks : Block [ ] , node : Element , stack : Element [ ] , to_encapsulate : any [ ] ) : boolean {
164
+ function apply_selector ( blocks : Block [ ] , node : Element , to_encapsulate : any [ ] ) : boolean {
153
165
const block = blocks . pop ( ) ;
154
166
if ( ! block ) return false ;
155
167
@@ -162,7 +174,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
162
174
return false ;
163
175
164
176
case BlockAppliesToNode . UnknownSelectorType :
165
- // bail. TODO figure out what these could be
177
+ // bail. TODO figure out what these could be
166
178
to_encapsulate . push ( { node, block } ) ;
167
179
return true ;
168
180
}
@@ -174,9 +186,10 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
174
186
continue ;
175
187
}
176
188
177
- for ( const stack_node of stack ) {
178
- if ( block_might_apply_to_node ( ancestor_block , stack_node ) !== BlockAppliesToNode . NotPossible ) {
179
- to_encapsulate . push ( { node : stack_node , block : ancestor_block } ) ;
189
+ let parent = node ;
190
+ while ( parent = get_element_parent ( parent ) ) {
191
+ if ( block_might_apply_to_node ( ancestor_block , parent ) !== BlockAppliesToNode . NotPossible ) {
192
+ to_encapsulate . push ( { node : parent , block : ancestor_block } ) ;
180
193
}
181
194
}
182
195
@@ -193,12 +206,32 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
193
206
194
207
return false ;
195
208
} else if ( block . combinator . name === '>' ) {
196
- if ( apply_selector ( blocks , stack . pop ( ) , stack , to_encapsulate ) ) {
209
+ if ( apply_selector ( blocks , get_element_parent ( node ) , to_encapsulate ) ) {
197
210
to_encapsulate . push ( { node, block } ) ;
198
211
return true ;
199
212
}
200
213
201
214
return false ;
215
+ } else if ( block . combinator . name === '+' ) {
216
+ const siblings = get_possible_element_siblings ( node , true ) ;
217
+ let has_match = false ;
218
+ for ( const possible_sibling of siblings ) {
219
+ if ( apply_selector ( blocks . slice ( ) , possible_sibling . element , to_encapsulate ) ) {
220
+ to_encapsulate . push ( { node, block } ) ;
221
+ has_match = true ;
222
+ }
223
+ }
224
+ return has_match ;
225
+ } else if ( block . combinator . name === '~' ) {
226
+ const siblings = get_possible_element_siblings ( node , false ) ;
227
+ let has_match = false ;
228
+ for ( const possible_sibling of siblings ) {
229
+ if ( apply_selector ( blocks . slice ( ) , possible_sibling . element , to_encapsulate ) ) {
230
+ to_encapsulate . push ( { node, block } ) ;
231
+ has_match = true ;
232
+ }
233
+ }
234
+ return has_match ;
202
235
}
203
236
204
237
// TODO other combinators
@@ -376,6 +409,127 @@ function unquote(value: CssNode) {
376
409
return str ;
377
410
}
378
411
412
+ function get_element_parent ( node : Element ) : Element | null {
413
+ let parent : INode = node ;
414
+ while ( ( parent = parent . parent ) && parent . type !== 'Element' ) ;
415
+ return parent as Element | null ;
416
+ }
417
+
418
+ function get_possible_element_siblings ( node : INode , adjacent_only : boolean ) : ElementAndExist [ ] {
419
+ const result : ElementAndExist [ ] = [ ] ;
420
+ let prev : INode = node ;
421
+ while ( ( prev = prev . prev ) && prev . type !== 'Element' ) {
422
+ if ( prev . type === 'EachBlock' || prev . type === 'IfBlock' || prev . type === 'AwaitBlock' ) {
423
+ const possible_last_child = get_possible_last_child ( prev , adjacent_only ) ;
424
+ result . push ( ...possible_last_child ) ;
425
+ if ( adjacent_only && possible_last_child . find ( child => child . exist === NodeExist . Definitely ) ) {
426
+ return result ;
427
+ }
428
+ }
429
+ }
430
+
431
+ if ( prev ) {
432
+ result . push ( { element : prev as Element , exist : NodeExist . Definitely } ) ;
433
+ }
434
+
435
+ if ( ! prev || ! adjacent_only ) {
436
+ let parent : INode = node ;
437
+ let else_block = node . type === 'ElseBlock' ;
438
+ while ( ( parent = parent . parent ) && ( parent . type === 'EachBlock' || parent . type === 'IfBlock' || parent . type === 'ElseBlock' || parent . type === 'AwaitBlock' ) ) {
439
+ const possible_siblings = get_possible_element_siblings ( parent , adjacent_only ) ;
440
+ result . push ( ...possible_siblings ) ;
441
+
442
+ if ( parent . type === 'EachBlock' ) {
443
+ if ( else_block ) {
444
+ else_block = false ;
445
+ } else {
446
+ for ( const each_parent_last_child of get_possible_last_child ( parent , adjacent_only ) ) {
447
+ result . push ( each_parent_last_child ) ;
448
+ }
449
+ }
450
+ } else if ( parent . type === 'ElseBlock' ) {
451
+ else_block = true ;
452
+ }
453
+
454
+ if ( adjacent_only && possible_siblings . find ( sibling => sibling . exist === NodeExist . Definitely ) ) {
455
+ break ;
456
+ }
457
+ }
458
+ }
459
+
460
+ return result ;
461
+ }
462
+
463
+ function get_possible_last_child ( block : EachBlock | IfBlock | AwaitBlock , adjacent_only : boolean ) : ElementAndExist [ ] {
464
+ const result = [ ] ;
465
+
466
+ if ( block . type === 'EachBlock' ) {
467
+ const each_result : ElementAndExist [ ] = loop_child ( block . children , adjacent_only ) ;
468
+ const else_result : ElementAndExist [ ] = block . else ? loop_child ( block . else . children , adjacent_only ) : [ ] ;
469
+
470
+ const not_exhaustive =
471
+ else_result . length === 0 || ! else_result . find ( result => result . exist === NodeExist . Definitely ) ;
472
+
473
+ if ( not_exhaustive ) {
474
+ each_result . forEach ( result => result . exist = NodeExist . Probably ) ;
475
+ else_result . forEach ( result => result . exist = NodeExist . Probably ) ;
476
+ }
477
+ result . push ( ...each_result , ...else_result ) ;
478
+ } else if ( block . type === 'IfBlock' ) {
479
+ const if_result : ElementAndExist [ ] = loop_child ( block . children , adjacent_only ) ;
480
+ const else_result : ElementAndExist [ ] = block . else ? loop_child ( block . else . children , adjacent_only ) : [ ] ;
481
+
482
+ const not_exhaustive =
483
+ if_result . length === 0 || ! if_result . find ( result => result . exist === NodeExist . Definitely ) ||
484
+ else_result . length === 0 || ! else_result . find ( result => result . exist === NodeExist . Definitely ) ;
485
+
486
+ if ( not_exhaustive ) {
487
+ if_result . forEach ( result => result . exist = NodeExist . Probably ) ;
488
+ else_result . forEach ( result => result . exist = NodeExist . Probably ) ;
489
+ }
490
+
491
+ result . push ( ...if_result , ...else_result ) ;
492
+ } else if ( block . type === 'AwaitBlock' ) {
493
+ const pending_result : ElementAndExist [ ] = block . pending ? loop_child ( block . pending . children , adjacent_only ) : [ ] ;
494
+ const then_result : ElementAndExist [ ] = block . then ? loop_child ( block . then . children , adjacent_only ) : [ ] ;
495
+ const catch_result : ElementAndExist [ ] = block . catch ? loop_child ( block . catch . children , adjacent_only ) : [ ] ;
496
+
497
+ const not_exhaustive =
498
+ pending_result . length === 0 || ! pending_result . find ( result => result . exist === NodeExist . Definitely ) ||
499
+ then_result . length === 0 || ! then_result . find ( result => result . exist === NodeExist . Definitely ) ||
500
+ catch_result . length === 0 || ! catch_result . find ( result => result . exist === NodeExist . Definitely ) ;
501
+
502
+ if ( not_exhaustive ) {
503
+ pending_result . forEach ( result => result . exist = NodeExist . Probably ) ;
504
+ then_result . forEach ( result => result . exist = NodeExist . Probably ) ;
505
+ catch_result . forEach ( result => result . exist = NodeExist . Probably ) ;
506
+ }
507
+ result . push ( ...pending_result , ...then_result , ...catch_result ) ;
508
+ }
509
+
510
+ return result ;
511
+ }
512
+
513
+ function loop_child ( children : INode [ ] , adjacent_only : boolean ) {
514
+ const result = [ ] ;
515
+ for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
516
+ const child = children [ i ] ;
517
+ if ( child . type === 'Element' ) {
518
+ result . push ( { element : child , exist : NodeExist . Definitely } ) ;
519
+ if ( adjacent_only ) {
520
+ break ;
521
+ }
522
+ } else if ( child . type === 'EachBlock' || child . type === 'IfBlock' || child . type === 'AwaitBlock' ) {
523
+ const child_result = get_possible_last_child ( child , adjacent_only ) ;
524
+ result . push ( ...child_result ) ;
525
+ if ( adjacent_only && child_result . find ( child => child . exist === NodeExist . Definitely ) ) {
526
+ break ;
527
+ }
528
+ }
529
+ }
530
+ return result ;
531
+ }
532
+
379
533
class Block {
380
534
global : boolean ;
381
535
combinator : CssNode ;
0 commit comments