1+ import { customElement , WebComponent } from '@/lib/components'
12import { html } from 'lit'
23import { property , query , state } from 'lit/decorators.js'
3- import { generateId , WebComponent , customElement } from '@/lib/components'
4- import ComboboxOption from '@/components/combobox-option/ComboboxOption'
4+ import InputTrait from '@/lib/components/traits/InputTrait '
5+ import type ComboboxOption from '@/components/combobox-option/ComboboxOption'
56
67import '~icons/lucide/chevron-down'
78
@@ -15,9 +16,18 @@ export default class Combobox extends WebComponent {
1516 @property ( { type : String , reflect : true } )
1617 accessor label = ''
1718
19+ @property ( { type : String , reflect : true } )
20+ accessor name = ''
21+
1822 @property ( { type : String } )
1923 accessor value = ''
2024
25+ @property ( { type : String , reflect : true } )
26+ accessor placeholder = ''
27+
28+ @property ( { type : Boolean , reflect : true } )
29+ accessor required = false
30+
2131 @query ( '[popover]' )
2232 private accessor popoverElement : HTMLDivElement | null = null
2333
@@ -27,42 +37,67 @@ export default class Combobox extends WebComponent {
2737 @state ( )
2838 private accessor filter = ''
2939
30- private inputId = `combobox- ${ generateId ( ) } `
40+ private inputTrait : InputTrait
3141
32- protected render ( ) {
33- const options = this . getOptions ( ) . filter ( option => option . toLowerCase ( ) . includes ( this . filter ) )
42+ constructor ( ) {
43+ super ( )
3444
35- return html `
36- ${ this . label ? html `< label for ="${ this . inputId } "> ${ this . label } </ label > ` : '' }
37- < div class ="input-wrapper ">
38- < input
39- id ="${ this . inputId } "
40- .value =${ this . value }
41- type ="text"
42- @keydown=${ this . onInputKeyDown }
43- @click=${ this . onInputClick }
44- @input=${ this . onInputChange }
45- />
46- < icon-lucide-chevron-down > </ icon-lucide-chevron-down >
47- </ div >
48- < div role ="listbox " aria-labelledby ="${ this . inputId } " popover >
49- ${ options . map ( option => html `< div role ="option " aria-selected ="false " @click =${ ( ) => this . onOptionClick ( option ) } > ${ option } </ div > ` ) }
50- </ div >
51- `
45+ this . inputTrait = this . addTrait (
46+ new InputTrait ( this , {
47+ getInputElement : ( ) => this . inputElement ,
48+ getInternals : ( ) => this . getInternals ( ) ,
49+ onValueChanged : ( value ) => {
50+ this . filter = value . toLowerCase ( )
51+ } ,
52+ } )
53+ )
5254 }
5355
54- private getOptions ( ) : string [ ] {
55- const options = this . querySelectorAll < ComboboxOption > ( 'solid-ui-combobox-option' )
56+ protected render ( ) {
57+ const options = this . getOptions ( ) . filter ( ( option ) =>
58+ option . label . toLowerCase ( ) . includes ( this . filter )
59+ )
5660
57- return Array . from ( options ) . map ( option => option . value )
61+ return html `
62+ ${ this . inputTrait . renderLabel ( ) }
63+ < div class ="input-wrapper ">
64+ < input
65+ id ="${ this . inputTrait . inputId } "
66+ type ="text "
67+ name =${ this . name }
68+ ?placeholder =${ this . placeholder }
69+ ?required=${ this . required }
70+ .value=${ this . value }
71+ @keydown=${ this . onInputKeyDown }
72+ @click=${ this . onInputClick }
73+ @input=${ ( ) => this . inputTrait . onInput ( ) }
74+ />
75+ < icon-lucide-chevron-down > </ icon-lucide-chevron-down >
76+ </ div >
77+ < div role ="listbox " aria-labelledby ="${ this . inputTrait . inputId } " popover >
78+ ${ options . map (
79+ ( option ) =>
80+ html `< div
81+ role ="option "
82+ aria-selected ="false "
83+ @click =${ ( ) => this . onOptionClick ( option . value ) }
84+ >
85+ ${ option . label }
86+ </ div > `
87+ ) }
88+ </ div >
89+ `
5890 }
5991
60- private setValue ( value : string ) {
61- this . filter = value . toLowerCase ( )
62- this . value = value
92+ private getOptions ( ) : { value : string ; label : string } [ ] {
93+ const options = this . querySelectorAll < ComboboxOption > (
94+ 'solid-ui-combobox-option'
95+ )
6396
64- this . getInternals ( ) . setFormValue ( value )
65- this . dispatchEvent ( new InputEvent ( 'input' , { bubbles : true , composed : true } ) )
97+ return Array . from ( options ) . map ( ( option ) => ( {
98+ value : option . value ,
99+ label : option . textContent ,
100+ } ) )
66101 }
67102
68103 private onInputKeyDown ( e : KeyboardEvent ) {
@@ -75,7 +110,7 @@ export default class Combobox extends WebComponent {
75110 if ( ! this . popoverElement ?. matches ( ':popover-open' ) ) {
76111 e . preventDefault ( )
77112
78- this . getInternals ( ) . form ?. requestSubmit ( )
113+ this . inputTrait . onSubmit ( )
79114 }
80115 break
81116 }
@@ -87,12 +122,8 @@ export default class Combobox extends WebComponent {
87122 this . popoverElement ?. showPopover ( )
88123 }
89124
90- private onInputChange ( ) {
91- this . setValue ( this . inputElement ?. value ?? '' )
92- }
93-
94125 private onOptionClick ( option : string ) {
95- this . setValue ( option )
126+ this . inputTrait . setValue ( option )
96127
97128 this . popoverElement ?. hidePopover ( )
98129 }
0 commit comments