@@ -4,25 +4,25 @@ import {
4
4
useContext ,
5
5
useContextProvider ,
6
6
Slot ,
7
- useClientEffect$ ,
8
7
useSignal ,
9
8
Signal ,
10
9
$ ,
11
- QRL ,
12
10
useOnWindow ,
13
11
useStore ,
12
+ useTask$ ,
14
13
} from '@builder.io/qwik' ;
15
14
import { computePosition , flip } from '@floating-ui/dom' ;
16
15
17
16
interface SelectRootContextService {
18
- options : HTMLElement [ ] ;
17
+ options : Signal < HTMLElement | undefined > [ ] ;
19
18
selectedOption : Signal < string > ;
20
19
isExpanded : Signal < boolean > ;
21
- setTriggerRef$ : QRL < ( ref : Signal < HTMLElement | undefined > ) => void > ;
22
- setListBoxRef$ : QRL < ( ref : Signal < HTMLElement | undefined > ) => void > ;
20
+ triggerRef : Signal < HTMLElement | undefined > ;
21
+ listBoxRef : Signal < HTMLElement | undefined > ;
23
22
}
24
23
25
- export const selectContext = createContext < SelectRootContextService > ( 'select-root' ) ;
24
+ export const selectContext =
25
+ createContext < SelectRootContextService > ( 'select-root' ) ;
26
26
27
27
interface StyleProps {
28
28
class ?: string ;
@@ -35,30 +35,17 @@ interface RootProps extends StyleProps {
35
35
36
36
const Root = component$ ( ( { defaultValue, ...props } : RootProps ) => {
37
37
const options = useStore ( [ ] ) ;
38
-
39
38
const selectedOption = useSignal ( defaultValue ? defaultValue : '' ) ;
40
39
const isExpanded = useSignal ( false ) ;
41
-
42
40
const triggerRef = useSignal < HTMLElement > ( ) ;
43
- const setTriggerRef$ = $ ( ( ref : Signal < HTMLElement | undefined > ) => {
44
- if ( ref ) {
45
- triggerRef . value = ref . value ;
46
- }
47
- } ) ;
48
-
49
41
const listBoxRef = useSignal < HTMLElement > ( ) ;
50
- const setListBoxRef$ = $ ( ( ref : Signal < HTMLElement | undefined > ) => {
51
- if ( ref ) {
52
- listBoxRef . value = ref . value ;
53
- }
54
- } ) ;
55
42
56
43
const contextService : SelectRootContextService = {
57
44
options,
58
45
selectedOption,
59
46
isExpanded,
60
- setTriggerRef$ ,
61
- setListBoxRef$ ,
47
+ triggerRef ,
48
+ listBoxRef ,
62
49
} ;
63
50
64
51
useContextProvider ( selectContext , contextService ) ;
@@ -77,9 +64,9 @@ const Root = component$(({ defaultValue, ...props }: RootProps) => {
77
64
}
78
65
) ;
79
66
80
- useClientEffect $( async ( { track } ) => {
81
- const trigger = track ( ( ) => triggerRef . value ) ;
82
- const listBox = track ( ( ) => listBoxRef . value ) ;
67
+ useTask $( async ( { track } ) => {
68
+ const trigger = track ( ( ) => contextService . triggerRef . value ) ;
69
+ const listBox = track ( ( ) => contextService . listBoxRef . value ) ;
83
70
const expanded = track ( ( ) => isExpanded . value ) ;
84
71
85
72
if ( expanded && trigger && listBox ) {
@@ -101,7 +88,7 @@ const Root = component$(({ defaultValue, ...props }: RootProps) => {
101
88
const target = e . target as HTMLElement ;
102
89
if (
103
90
contextService . isExpanded . value === true &&
104
- e . target !== triggerRef . value &&
91
+ e . target !== contextService . triggerRef . value &&
105
92
target . getAttribute ( 'role' ) !== 'option' &&
106
93
target . nodeName !== 'LABEL'
107
94
) {
@@ -131,10 +118,7 @@ interface TriggerProps extends StyleProps {
131
118
const Trigger = component$ ( ( { disabled, ...props } : TriggerProps ) => {
132
119
const ref = useSignal < HTMLElement > ( ) ;
133
120
const contextService = useContext ( selectContext ) ;
134
-
135
- useClientEffect$ ( ( ) => {
136
- contextService . setTriggerRef$ ( ref ) ;
137
- } ) ;
121
+ contextService . triggerRef = ref ;
138
122
139
123
return (
140
124
< button
@@ -177,15 +161,7 @@ const Marker = component$(({ ...props }: StyleProps) => {
177
161
const ListBox = component$ ( ( { ...props } : StyleProps ) => {
178
162
const ref = useSignal < HTMLElement > ( ) ;
179
163
const contextService = useContext ( selectContext ) ;
180
-
181
- useClientEffect$ ( ( ) => {
182
- contextService . setListBoxRef$ ( ref ) ;
183
- const options = ref . value ?. querySelectorAll < HTMLElement > ( '[role="option"]' ) ;
184
- if ( options ?. length ) {
185
- options . forEach ( ( option ) => contextService . options . push ( option ) ) ;
186
- }
187
- } ) ;
188
-
164
+ contextService . listBoxRef = ref ;
189
165
return (
190
166
< ul
191
167
ref = { ref }
@@ -199,25 +175,27 @@ const ListBox = component$(({ ...props }: StyleProps) => {
199
175
` }
200
176
class = { props . class }
201
177
onKeyDown$ = { ( e ) => {
202
- const availableOptions = contextService . options . filter (
203
- ( option ) => ! ( option . getAttribute ( 'aria-disabled' ) === 'true' )
204
- ) ;
178
+ const availableOptions = contextService . options
179
+ . map ( ( option ) => option . value )
180
+ . filter (
181
+ ( option ) => ! ( option ?. getAttribute ( 'aria-disabled' ) === 'true' )
182
+ ) ;
205
183
const target = e . target as HTMLElement ;
206
184
const currentIndex = availableOptions . indexOf ( target ) ;
207
185
208
186
if ( e . key === 'ArrowDown' ) {
209
187
if ( currentIndex === availableOptions . length - 1 ) {
210
- availableOptions [ 0 ] . focus ( ) ;
188
+ availableOptions [ 0 ] ? .focus ( ) ;
211
189
} else {
212
- availableOptions [ currentIndex + 1 ] . focus ( ) ;
190
+ availableOptions [ currentIndex + 1 ] ? .focus ( ) ;
213
191
}
214
192
}
215
193
216
194
if ( e . key === 'ArrowUp' ) {
217
195
if ( currentIndex <= 0 ) {
218
- availableOptions [ availableOptions . length - 1 ] . focus ( ) ;
196
+ availableOptions [ availableOptions . length - 1 ] ? .focus ( ) ;
219
197
} else {
220
- availableOptions [ currentIndex - 1 ] . focus ( ) ;
198
+ availableOptions [ currentIndex - 1 ] ? .focus ( ) ;
221
199
}
222
200
}
223
201
} }
@@ -254,9 +232,11 @@ interface OptionProps extends StyleProps {
254
232
255
233
const Option = component$ ( ( { disabled, value, ...props } : OptionProps ) => {
256
234
const contextService = useContext ( selectContext ) ;
257
-
235
+ const thisOptionSignal = useSignal < HTMLElement > ( ) ;
236
+ contextService . options = [ ...contextService . options , thisOptionSignal ] ;
258
237
return (
259
238
< li
239
+ ref = { thisOptionSignal }
260
240
role = "option"
261
241
tabIndex = { disabled ? - 1 : 0 }
262
242
aria-disabled = { disabled }
@@ -298,13 +278,4 @@ const Option = component$(({ disabled, value, ...props }: OptionProps) => {
298
278
) ;
299
279
} ) ;
300
280
301
- export {
302
- Root ,
303
- Trigger ,
304
- Value ,
305
- Marker ,
306
- ListBox ,
307
- Group ,
308
- Label ,
309
- Option ,
310
- } ;
281
+ export { Root , Trigger , Value , Marker , ListBox , Group , Label , Option } ;
0 commit comments