@@ -4,12 +4,20 @@ 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 = 1 ,
19
+ Definitely = 2 ,
20
+ }
13
21
14
22
const whitelist_attribute_selector = new Map ( [
15
23
[ 'details' , new Set ( [ 'open' ] ) ]
@@ -39,10 +47,10 @@ export default class Selector {
39
47
this . used = this . local_blocks . length === 0 ;
40
48
}
41
49
42
- apply ( node : Element , stack : Element [ ] ) {
50
+ apply ( node : Element ) {
43
51
const to_encapsulate : any [ ] = [ ] ;
44
52
45
- apply_selector ( this . local_blocks . slice ( ) , node , stack . slice ( ) , to_encapsulate ) ;
53
+ apply_selector ( this . local_blocks . slice ( ) , node , to_encapsulate ) ;
46
54
47
55
if ( to_encapsulate . length > 0 ) {
48
56
to_encapsulate . forEach ( ( { node, block } ) => {
@@ -149,7 +157,7 @@ export default class Selector {
149
157
}
150
158
}
151
159
152
- function apply_selector ( blocks : Block [ ] , node : Element , stack : Element [ ] , to_encapsulate : any [ ] ) : boolean {
160
+ function apply_selector ( blocks : Block [ ] , node : Element , to_encapsulate : any [ ] ) : boolean {
153
161
const block = blocks . pop ( ) ;
154
162
if ( ! block ) return false ;
155
163
@@ -162,7 +170,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
162
170
return false ;
163
171
164
172
case BlockAppliesToNode . UnknownSelectorType :
165
- // bail. TODO figure out what these could be
173
+ // bail. TODO figure out what these could be
166
174
to_encapsulate . push ( { node, block } ) ;
167
175
return true ;
168
176
}
@@ -174,9 +182,10 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
174
182
continue ;
175
183
}
176
184
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 } ) ;
185
+ let parent = node ;
186
+ while ( parent = get_element_parent ( parent ) ) {
187
+ if ( block_might_apply_to_node ( ancestor_block , parent ) !== BlockAppliesToNode . NotPossible ) {
188
+ to_encapsulate . push ( { node : parent , block : ancestor_block } ) ;
180
189
}
181
190
}
182
191
@@ -193,12 +202,22 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc
193
202
194
203
return false ;
195
204
} else if ( block . combinator . name === '>' ) {
196
- if ( apply_selector ( blocks , stack . pop ( ) , stack , to_encapsulate ) ) {
205
+ if ( apply_selector ( blocks , get_element_parent ( node ) , to_encapsulate ) ) {
197
206
to_encapsulate . push ( { node, block } ) ;
198
207
return true ;
199
208
}
200
209
201
210
return false ;
211
+ } else if ( block . combinator . name === '+' || block . combinator . name === '~' ) {
212
+ const siblings = get_possible_element_siblings ( node , block . combinator . name === '+' ) ;
213
+ let has_match = false ;
214
+ for ( const possible_sibling of siblings . keys ( ) ) {
215
+ if ( apply_selector ( blocks . slice ( ) , possible_sibling , to_encapsulate ) ) {
216
+ to_encapsulate . push ( { node, block } ) ;
217
+ has_match = true ;
218
+ }
219
+ }
220
+ return has_match ;
202
221
}
203
222
204
223
// TODO other combinators
@@ -376,6 +395,158 @@ function unquote(value: CssNode) {
376
395
return str ;
377
396
}
378
397
398
+ function get_element_parent ( node : Element ) : Element | null {
399
+ let parent : INode = node ;
400
+ while ( ( parent = parent . parent ) && parent . type !== 'Element' ) ;
401
+ return parent as Element | null ;
402
+ }
403
+
404
+ function get_possible_element_siblings ( node : INode , adjacent_only : boolean ) : Map < Element , NodeExist > {
405
+ const result : Map < Element , NodeExist > = new Map ( ) ;
406
+ let prev : INode = node ;
407
+ while ( prev = prev . prev ) {
408
+ if ( prev . type === 'Element' ) {
409
+ if ( ! prev . attributes . find ( attr => attr . name . toLowerCase ( ) === 'slot' ) ) {
410
+ result . set ( prev , NodeExist . Definitely ) ;
411
+ }
412
+
413
+ if ( adjacent_only ) {
414
+ break ;
415
+ }
416
+ } else if ( prev . type === 'EachBlock' || prev . type === 'IfBlock' || prev . type === 'AwaitBlock' ) {
417
+ const possible_last_child = get_possible_last_child ( prev , adjacent_only ) ;
418
+
419
+ add_to_map ( possible_last_child , result ) ;
420
+ if ( adjacent_only && has_definite_elements ( possible_last_child ) ) {
421
+ return result ;
422
+ }
423
+ }
424
+ }
425
+
426
+ if ( ! prev || ! adjacent_only ) {
427
+ let parent : INode = node ;
428
+ let skip_each_for_last_child = node . type === 'ElseBlock' ;
429
+ while ( ( parent = parent . parent ) && ( parent . type === 'EachBlock' || parent . type === 'IfBlock' || parent . type === 'ElseBlock' || parent . type === 'AwaitBlock' ) ) {
430
+ const possible_siblings = get_possible_element_siblings ( parent , adjacent_only ) ;
431
+ add_to_map ( possible_siblings , result ) ;
432
+
433
+ if ( parent . type === 'EachBlock' ) {
434
+ // first child of each block can select the last child of each block as previous sibling
435
+ if ( skip_each_for_last_child ) {
436
+ skip_each_for_last_child = false ;
437
+ } else {
438
+ add_to_map ( get_possible_last_child ( parent , adjacent_only ) , result ) ;
439
+ }
440
+ } else if ( parent . type === 'ElseBlock' ) {
441
+ skip_each_for_last_child = true ;
442
+ parent = parent . parent ;
443
+ }
444
+
445
+ if ( adjacent_only && has_definite_elements ( possible_siblings ) ) {
446
+ break ;
447
+ }
448
+ }
449
+ }
450
+
451
+ return result ;
452
+ }
453
+
454
+ function get_possible_last_child ( block : EachBlock | IfBlock | AwaitBlock , adjacent_only : boolean ) : Map < Element , NodeExist > {
455
+ const result : Map < Element , NodeExist > = new Map ( ) ;
456
+
457
+ if ( block . type === 'EachBlock' ) {
458
+ const each_result : Map < Element , NodeExist > = loop_child ( block . children , adjacent_only ) ;
459
+ const else_result : Map < Element , NodeExist > = block . else ? loop_child ( block . else . children , adjacent_only ) : new Map ( ) ;
460
+
461
+ const not_exhaustive = ! has_definite_elements ( else_result ) ;
462
+
463
+ if ( not_exhaustive ) {
464
+ mark_as_probably ( each_result ) ;
465
+ mark_as_probably ( else_result ) ;
466
+ }
467
+ add_to_map ( each_result , result ) ;
468
+ add_to_map ( else_result , result ) ;
469
+ } else if ( block . type === 'IfBlock' ) {
470
+ const if_result : Map < Element , NodeExist > = loop_child ( block . children , adjacent_only ) ;
471
+ const else_result : Map < Element , NodeExist > = block . else ? loop_child ( block . else . children , adjacent_only ) : new Map ( ) ;
472
+
473
+ const not_exhaustive = ! has_definite_elements ( if_result ) || ! has_definite_elements ( else_result ) ;
474
+
475
+ if ( not_exhaustive ) {
476
+ mark_as_probably ( if_result ) ;
477
+ mark_as_probably ( else_result ) ;
478
+ }
479
+
480
+ add_to_map ( if_result , result ) ;
481
+ add_to_map ( else_result , result ) ;
482
+ } else if ( block . type === 'AwaitBlock' ) {
483
+ const pending_result : Map < Element , NodeExist > = block . pending ? loop_child ( block . pending . children , adjacent_only ) : new Map ( ) ;
484
+ const then_result : Map < Element , NodeExist > = block . then ? loop_child ( block . then . children , adjacent_only ) : new Map ( ) ;
485
+ const catch_result : Map < Element , NodeExist > = block . catch ? loop_child ( block . catch . children , adjacent_only ) : new Map ( ) ;
486
+
487
+ const not_exhaustive = ! has_definite_elements ( pending_result ) || ! has_definite_elements ( then_result ) || ! has_definite_elements ( catch_result ) ;
488
+
489
+ if ( not_exhaustive ) {
490
+ mark_as_probably ( pending_result ) ;
491
+ mark_as_probably ( then_result ) ;
492
+ mark_as_probably ( catch_result ) ;
493
+ }
494
+
495
+ add_to_map ( pending_result , result ) ;
496
+ add_to_map ( then_result , result ) ;
497
+ add_to_map ( catch_result , result ) ;
498
+ }
499
+
500
+ return result ;
501
+ }
502
+
503
+ function has_definite_elements ( result : Map < Element , NodeExist > ) : boolean {
504
+ if ( result . size === 0 ) return false ;
505
+ for ( const exist of result . values ( ) ) {
506
+ if ( exist === NodeExist . Definitely ) {
507
+ return true ;
508
+ }
509
+ }
510
+ return false ;
511
+ }
512
+
513
+ function add_to_map ( from : Map < Element , NodeExist > , to : Map < Element , NodeExist > ) {
514
+ from . forEach ( ( exist , element ) => {
515
+ to . set ( element , higher_existance ( exist , to . get ( element ) ) ) ;
516
+ } ) ;
517
+ }
518
+
519
+ function higher_existance ( exist1 : NodeExist | null , exist2 : NodeExist | null ) : NodeExist {
520
+ if ( exist1 === undefined || exist2 === undefined ) return exist1 || exist2 ;
521
+ return exist1 > exist2 ? exist1 : exist2 ;
522
+ }
523
+
524
+ function mark_as_probably ( result : Map < Element , NodeExist > ) {
525
+ for ( const key of result . keys ( ) ) {
526
+ result . set ( key , NodeExist . Probably ) ;
527
+ }
528
+ }
529
+
530
+ function loop_child ( children : INode [ ] , adjacent_only : boolean ) {
531
+ const result : Map < Element , NodeExist > = new Map ( ) ;
532
+ for ( let i = children . length - 1 ; i >= 0 ; i -- ) {
533
+ const child = children [ i ] ;
534
+ if ( child . type === 'Element' ) {
535
+ result . set ( child , NodeExist . Definitely ) ;
536
+ if ( adjacent_only ) {
537
+ break ;
538
+ }
539
+ } else if ( child . type === 'EachBlock' || child . type === 'IfBlock' || child . type === 'AwaitBlock' ) {
540
+ const child_result = get_possible_last_child ( child , adjacent_only ) ;
541
+ add_to_map ( child_result , result ) ;
542
+ if ( adjacent_only && has_definite_elements ( child_result ) ) {
543
+ break ;
544
+ }
545
+ }
546
+ }
547
+ return result ;
548
+ }
549
+
379
550
class Block {
380
551
global : boolean ;
381
552
combinator : CssNode ;
0 commit comments