@@ -5,15 +5,14 @@ import {
5
5
Element ,
6
6
Event ,
7
7
type EventEmitter ,
8
- Fragment ,
9
8
Host ,
10
9
Method ,
11
10
Prop ,
12
11
State ,
13
12
Watch ,
14
13
h ,
15
14
} from '@stencil/core' ;
16
- import { ENTER , ESC , SPACE } from 'key-definitions' ;
15
+ import { ARROW_DOWN , ARROW_UP , ENTER , ESC , SPACE } from 'key-definitions' ;
17
16
import type { FormAssociatedInterface , Size } from 'src/interface' ;
18
17
import { componentConfig , config } from '#config' ;
19
18
import { ClickOutside } from '#utils/click-outside' ;
@@ -50,6 +49,8 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
50
49
@Element ( ) host ! : HTMLElement ;
51
50
@AttachInternals ( ) internals : ElementInternals ;
52
51
52
+ @State ( ) options : HTMLPopSelectOptionElement [ ] = [ ] ;
53
+
53
54
@State ( ) open = false ;
54
55
55
56
@State ( ) errorText : string ;
@@ -382,6 +383,11 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
382
383
} ;
383
384
} ;
384
385
386
+ private onSlotChange = ( ) => {
387
+ const options = this . host . querySelectorAll ( 'pop-select-option' ) ;
388
+ this . options = [ ...Array . from ( options ) ] ;
389
+ } ;
390
+
385
391
private get values ( ) : any [ ] {
386
392
const { value } = this ;
387
393
if ( value == null ) return [ ] ;
@@ -415,10 +421,6 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
415
421
return '' ;
416
422
}
417
423
418
- private get options ( ) {
419
- return Array . from ( this . host . querySelectorAll ( 'pop-select-option' ) ) ;
420
- }
421
-
422
424
private getAriaLabel ( text : string ) : string {
423
425
const { placeholder } = this ;
424
426
const displayValue = text ;
@@ -484,19 +486,36 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
484
486
} }
485
487
value = { this . value }
486
488
>
487
- { this . options . map ( option => (
488
- < pop-item >
489
- < pop-radio
490
- checked = { isOptionSelected ( this . value , getOptionValue ( option ) , this . compare ) }
491
- color = { color === 'ghost' ? undefined : color }
492
- disabled = { option . disabled }
493
- size = { size }
494
- value = { getOptionValue ( option ) }
495
- >
496
- { option . textContent }
497
- </ pop-radio >
498
- </ pop-item >
499
- ) ) }
489
+ < pop-list >
490
+ { this . options . map ( ( option , idx ) => (
491
+ < pop-item >
492
+ < pop-radio
493
+ checked = { isOptionSelected ( this . value , getOptionValue ( option ) , this . compare ) }
494
+ color = { color === 'ghost' ? undefined : color }
495
+ disabled = { option . disabled }
496
+ onKeyUp = { async ev => {
497
+ if ( ev . key !== ARROW_DOWN . key && ev . key !== ARROW_UP . key ) {
498
+ return ;
499
+ }
500
+ ev . preventDefault ( ) ;
501
+ const radios = this . dropdownRef ?. querySelectorAll ( 'pop-radio' ) ?? [ ] ;
502
+ if ( radios . length === 0 ) {
503
+ return ;
504
+ }
505
+
506
+ const previous = idx === 0 ? this . options . length - 1 : idx - 1 ;
507
+ const next = idx === this . options . length - 1 ? 0 : idx + 1 ;
508
+ const index = ev . key === ARROW_UP . key ? previous : next ;
509
+ return Array . from ( radios ) . at ( index ) . setFocus ( ) ;
510
+ } }
511
+ size = { size }
512
+ value = { getOptionValue ( option ) }
513
+ >
514
+ { option . textContent }
515
+ </ pop-radio >
516
+ </ pop-item >
517
+ ) ) }
518
+ </ pop-list >
500
519
</ pop-radio-group >
501
520
) ;
502
521
}
@@ -505,13 +524,28 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
505
524
const { color, size } = this ;
506
525
507
526
return (
508
- < Fragment >
509
- { this . options . map ( option => (
527
+ < pop-list >
528
+ { this . options . map ( ( option , idx ) => (
510
529
< pop-item >
511
530
< pop-checkbox
512
531
checked = { isOptionSelected ( this . value , getOptionValue ( option ) , this . compare ) }
513
532
color = { color === 'ghost' ? undefined : color }
514
533
disabled = { option . disabled ?? this . disabled }
534
+ onKeyUp = { async ev => {
535
+ if ( ev . key !== ARROW_DOWN . key && ev . key !== ARROW_UP . key ) {
536
+ return ;
537
+ }
538
+ ev . preventDefault ( ) ;
539
+ const checkboxes = this . dropdownRef ?. querySelectorAll ( 'pop-checkbox' ) ?? [ ] ;
540
+ if ( checkboxes . length === 0 ) {
541
+ return ;
542
+ }
543
+
544
+ const previous = idx === 0 ? this . options . length - 1 : idx - 1 ;
545
+ const next = idx === this . options . length - 1 ? 0 : idx + 1 ;
546
+ const index = ev . key === ARROW_UP . key ? previous : next ;
547
+ return Array . from ( checkboxes ) . at ( index ) . setFocus ( ) ;
548
+ } }
515
549
onPopChange = { async ( ) => {
516
550
this . errorText = this . errorTextValue ;
517
551
if ( this . errorText ) {
@@ -530,7 +564,7 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
530
564
</ pop-checkbox >
531
565
</ pop-item >
532
566
) ) }
533
- </ Fragment >
567
+ </ pop-list >
534
568
) ;
535
569
}
536
570
@@ -594,7 +628,7 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
594
628
class = "dropdown-content"
595
629
part = "content"
596
630
>
597
- < pop-list > { this . multiple ? this . renderCheckboxOptions ( ) : this . renderRadioOptions ( ) } </ pop-list >
631
+ { this . multiple ? this . renderCheckboxOptions ( ) : this . renderRadioOptions ( ) }
598
632
</ div >
599
633
{ /* biome-ignore lint/a11y/useKeyWithClickEvents: Element not focusable, handle by summary keyboard event */ }
600
634
< div
@@ -614,6 +648,8 @@ export class Select implements ComponentInterface, FormAssociatedInterface {
614
648
</ Show >
615
649
</ div >
616
650
</ Show >
651
+
652
+ < slot onSlotchange = { this . onSlotChange } />
617
653
</ Host >
618
654
) ;
619
655
}
0 commit comments