@@ -64,49 +64,6 @@ export type TabInfo = {
64
64
tabProps : TabProps ;
65
65
panelProps : TabPanelProps ;
66
66
} ;
67
- export const isDisabled = ( i : TabInfo ) => i . tabProps . disabled ;
68
- // Find an enabled tab including the index
69
- export const findEnabledTab = ( tabs : TabInfo [ ] , index : number , wrap ?: boolean ) => {
70
- let info ;
71
- for ( let i = Math . max ( 0 , index ) ; i < tabs . length ; i ++ ) {
72
- info = tabs [ i ] ;
73
- if ( ! isDisabled ( info ) ) {
74
- return info ;
75
- }
76
- }
77
- if ( wrap ) {
78
- for ( let i = 0 ; i < index ; i ++ ) {
79
- info = tabs [ i ] ;
80
- if ( ! isDisabled ( info ) ) {
81
- return info ;
82
- }
83
- }
84
- }
85
- return ;
86
- } ;
87
-
88
- // Find an enabled tab before the index
89
- export const findPrevEnabledTab = ( tabs : TabInfo [ ] , index : number , wrap ?: boolean ) => {
90
- let info ;
91
- for ( let i = Math . min ( tabs . length , index ) - 1 ; i >= 0 ; i -- ) {
92
- info = tabs [ i ] ;
93
- if ( ! isDisabled ( info ) ) {
94
- return info ;
95
- }
96
- }
97
- if ( wrap ) {
98
- for ( let i = tabs . length - 1 ; i > index ; i -- ) {
99
- info = tabs [ i ] ;
100
- if ( ! isDisabled ( info ) ) {
101
- return info ;
102
- }
103
- }
104
- }
105
- return ;
106
- } ;
107
-
108
- export const getEnabledTab = ( tabInfoList : TabInfo [ ] , index : number ) =>
109
- findEnabledTab ( tabInfoList , index ) || findPrevEnabledTab ( tabInfoList , index ) ;
110
67
111
68
// This function reads the children, assigns indexes and creates a
112
69
// standard structure. It must take care to retain the props objects
@@ -119,7 +76,7 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
119
76
let tabListElement : JSXNode | undefined ;
120
77
const tabComponents : JSXNode [ ] = [ ] ;
121
78
const panelComponents : JSXNode [ ] = [ ] ;
122
- const tabsInfo : TabInfo [ ] = [ ] ;
79
+ const tabInfoList : TabInfo [ ] = [ ] ;
123
80
let panelIndex = 0 ;
124
81
let selectedIndex ;
125
82
@@ -173,7 +130,7 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
173
130
child . props . _extraClass = panelClass ;
174
131
175
132
panelComponents . push ( child ) ;
176
- tabsInfo . push ( {
133
+ tabInfoList . push ( {
177
134
tabId,
178
135
index : panelIndex ,
179
136
panelProps : child . props
@@ -195,11 +152,11 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
195
152
}
196
153
197
154
tabComponents . forEach ( ( tab , index ) => {
198
- const tabId = tabsInfo [ index ] ?. tabId ;
155
+ const tabId = tabInfoList [ index ] ?. tabId ;
199
156
tab . key = tabId ;
200
157
tab . props . _tabId = tabId ;
201
158
tab . props . _extraClass = tabClass ;
202
- tabsInfo [ index ] . tabProps = tab . props ;
159
+ tabInfoList [ index ] . tabProps = tab . props ;
203
160
} ) ;
204
161
205
162
if ( tabListElement ) {
@@ -215,47 +172,18 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
215
172
}
216
173
217
174
return (
218
- < TabsImpl tabs = { tabsInfo } { ...rest } >
175
+ < TabsImpl tabInfoList = { tabInfoList } { ...rest } >
219
176
{ tabListElement }
220
177
{ panelComponents }
221
178
</ TabsImpl >
222
179
) ;
223
180
} ;
224
181
225
- // This helper function is separate so that it doesn't have to be a QRL
226
- // and it doesn't result in race conditions between tasks
227
- // We were seeing tabId signal task running before updateSignals when it was a QRL
228
- export const updateSignals = (
229
- tabs : TabInfo [ ] ,
230
- indexSig : Signal < number | undefined > ,
231
- tabIdSig : Signal < string | undefined > ,
232
- { idx, tabId } : { idx ?: number ; tabId ?: string } ,
233
- tryHarder ?: boolean
234
- ) => {
235
- if ( tabId ) {
236
- idx = tabs . findIndex ( ( t ) => t . tabId === tabId ) ;
237
- }
238
- if ( typeof idx !== 'number' ) return ;
239
- if ( idx && idx < 0 ) {
240
- if ( ! tryHarder ) {
241
- return ;
242
- }
243
- // given index doesn't exist, find one nearby
244
- idx = indexSig . value ;
245
- if ( typeof idx !== 'number' ) return ;
246
- }
247
- const tab = getEnabledTab ( tabs , idx ) ;
248
- if ( tab && ( tab . index !== indexSig . value || tab . tabId !== tabIdSig . value ) ) {
249
- indexSig . value = tab . index ;
250
- tabIdSig . value = tab . tabId ;
251
- }
252
- } ;
253
-
254
- export const TabsImpl = component$ ( ( props : TabsProps & { tabs : TabInfo [ ] } ) => {
182
+ export const TabsImpl = component$ ( ( props : TabsProps & { tabInfoList : TabInfo [ ] } ) => {
255
183
const {
256
184
// We take these out of the props for the DOM element but we must refer
257
185
// to them as e.g. props.tabs for reactivity
258
- tabs : _0 ,
186
+ tabInfoList : _0 ,
259
187
behavior = 'manual' ,
260
188
selectedTabId : _1 ,
261
189
selectedIndex : _2 ,
@@ -278,35 +206,49 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
278
206
useTask$ ( function syncTabsTask ( { track } ) {
279
207
// Possible optimizer bug: tracking only works with props.tabs
280
208
// TODO: Write a test in Qwik optimizer to prove this bug
281
- const tabs = track ( ( ) => props . tabs ) ;
209
+ const tabInfoList = track ( ( ) => props . tabInfoList ) ;
282
210
const tabId = selectedTabIdSig . value ;
283
- updateSignals ( tabs , selectedIndexSig , selectedTabIdSig , { tabId } , true ) ;
211
+ syncSelectedStateSignals (
212
+ tabInfoList ,
213
+ selectedIndexSig ,
214
+ selectedTabIdSig ,
215
+ { tabIdToSelect : tabId } ,
216
+ true
217
+ ) ;
284
218
} ) ;
285
219
useTask$ ( function syncPropSelectedIndexTask ( { track } ) {
286
- const idx = track ( ( ) => props . selectedIndex ) ;
287
- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { idx } ) ;
220
+ const updatedIndexFromProps = track ( ( ) => props . selectedIndex ) ;
221
+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
222
+ indexToSelect : updatedIndexFromProps
223
+ } ) ;
288
224
} ) ;
289
225
useTask$ ( function syncSelectedIndexSigTask ( { track } ) {
290
- const idx = track ( ( ) => selectedIndexSig . value ) ;
291
- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { idx } ) ;
226
+ const updatedIndexSignal = track ( ( ) => selectedIndexSig . value ) ;
227
+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
228
+ indexToSelect : updatedIndexSignal
229
+ } ) ;
292
230
if ( typeof selectedIndexSig . value !== 'undefined' ) {
293
231
onSelectedIndexChange$ ?.( selectedIndexSig . value ) ;
294
232
}
295
233
} ) ;
296
234
useTask$ ( function syncPropSelectedTabIdTask ( { track } ) {
297
- const tabId = track ( ( ) => props . selectedTabId ) ;
298
- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { tabId } ) ;
235
+ const updatedTabIdFromProps = track ( ( ) => props . selectedTabId ) ;
236
+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
237
+ tabIdToSelect : updatedTabIdFromProps
238
+ } ) ;
299
239
} ) ;
300
240
useTask$ ( function syncSelectedTabIdSigTask ( { track } ) {
301
- let tabId = track ( ( ) => selectedTabIdSig . value ) ;
241
+ let updatedTabId = track ( ( ) => selectedTabIdSig . value ) ;
302
242
// If we don't have a tabId by the time this task runs, select the first enabled tab
303
- if ( typeof tabId !== 'string' ) {
304
- const tab = getEnabledTab ( props . tabs , 0 ) ;
243
+ if ( typeof updatedTabId !== 'string' ) {
244
+ const tab = getEnabledTab ( props . tabInfoList , 0 ) ;
305
245
if ( tab ) {
306
- tabId = tab . tabId ;
246
+ updatedTabId = tab . tabId ;
307
247
}
308
248
}
309
- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { tabId } ) ;
249
+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
250
+ tabIdToSelect : updatedTabId
251
+ } ) ;
310
252
if ( typeof selectedTabIdSig . value !== 'undefined' ) {
311
253
onSelectedTabIdChange$ ?.( selectedTabIdSig . value ) ;
312
254
}
@@ -319,7 +261,9 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
319
261
} ) ;
320
262
321
263
const selectTab$ = $ ( ( tabId : string ) => {
322
- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { tabId } ) ;
264
+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
265
+ tabIdToSelect : tabId
266
+ } ) ;
323
267
} ) ;
324
268
325
269
const selectIfAutomatic$ = $ ( ( tabId : string ) => {
@@ -331,28 +275,28 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
331
275
const onTabKeyDown$ = $ ( ( key : KeyCode , currentTabId : string ) => {
332
276
const tabsRootElement = ref . value ;
333
277
334
- const currentFocusedTabIndex = props . tabs . findIndex (
278
+ const currentFocusedTabIndex = props . tabInfoList . findIndex (
335
279
( tabData ) => tabData . tabId === currentTabId
336
280
) ;
337
281
338
- let tab ;
282
+ let tabInfo ;
339
283
if (
340
284
( ! vertical && key === KeyCode . ArrowRight ) ||
341
285
( vertical && key === KeyCode . ArrowDown )
342
286
) {
343
- tab = findEnabledTab ( props . tabs , currentFocusedTabIndex + 1 , true ) ;
287
+ tabInfo = findNextEnabledTab ( props . tabInfoList , currentFocusedTabIndex + 1 , true ) ;
344
288
} else if (
345
289
( ! vertical && key === KeyCode . ArrowLeft ) ||
346
290
( vertical && key === KeyCode . ArrowUp )
347
291
) {
348
- tab = findPrevEnabledTab ( props . tabs , currentFocusedTabIndex , true ) ;
292
+ tabInfo = findPrevEnabledTab ( props . tabInfoList , currentFocusedTabIndex , true ) ;
349
293
} else if ( key === KeyCode . Home || key === KeyCode . PageUp ) {
350
- tab = findEnabledTab ( props . tabs , 0 ) ;
294
+ tabInfo = findNextEnabledTab ( props . tabInfoList , 0 ) ;
351
295
} else if ( key === KeyCode . End || key === KeyCode . PageDown ) {
352
- tab = findPrevEnabledTab ( props . tabs , props . tabs . length ) ;
296
+ tabInfo = findPrevEnabledTab ( props . tabInfoList , props . tabInfoList . length ) ;
353
297
}
354
- if ( tab ) {
355
- focusOnTab ( tab . index ) ;
298
+ if ( tabInfo ) {
299
+ focusOnTab ( tabInfo . index ) ;
356
300
}
357
301
358
302
function focusOnTab ( index : number ) {
@@ -379,3 +323,89 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
379
323
</ div >
380
324
) ;
381
325
} ) ;
326
+
327
+ // This helper function is separate so that it doesn't have to be a QRL
328
+ // and it doesn't result in race conditions between tasks
329
+ // We were seeing tabId signal task running before updateSignals when it was a QRL
330
+ export const syncSelectedStateSignals = (
331
+ tabsInfoList : TabInfo [ ] ,
332
+ selectedIndexSig : Signal < number | undefined > ,
333
+ selectedTabIdSig : Signal < string | undefined > ,
334
+ { indexToSelect, tabIdToSelect } : { indexToSelect ?: number ; tabIdToSelect ?: string } ,
335
+ ignoreIndexNotFound ?: boolean
336
+ ) => {
337
+ if ( tabIdToSelect ) {
338
+ indexToSelect = tabsInfoList . findIndex ( ( tabInfo ) => tabInfo . tabId === tabIdToSelect ) ;
339
+ }
340
+ if ( typeof indexToSelect !== 'number' ) return ;
341
+
342
+ if ( indexToSelect && indexToSelect < 0 ) {
343
+ if ( ! ignoreIndexNotFound ) {
344
+ return ;
345
+ }
346
+ // given index doesn't exist, find one nearby
347
+ indexToSelect = selectedIndexSig . value ;
348
+ if ( typeof indexToSelect !== 'number' ) return ;
349
+ }
350
+ const tab = getEnabledTab ( tabsInfoList , indexToSelect ) ;
351
+ if (
352
+ tab &&
353
+ ( tab . index !== selectedIndexSig . value || tab . tabId !== selectedTabIdSig . value )
354
+ ) {
355
+ selectedIndexSig . value = tab . index ;
356
+ selectedTabIdSig . value = tab . tabId ;
357
+ }
358
+ } ;
359
+
360
+ export const getEnabledTab = ( tabInfoList : TabInfo [ ] , index : number ) =>
361
+ findNextEnabledTab ( tabInfoList , index ) || findPrevEnabledTab ( tabInfoList , index ) ;
362
+
363
+ // Find an enabled tab including the index
364
+ export const findNextEnabledTab = (
365
+ tabsInfo : TabInfo [ ] ,
366
+ index : number ,
367
+ wrap ?: boolean
368
+ ) => {
369
+ let info ;
370
+ for ( let i = Math . max ( 0 , index ) ; i < tabsInfo . length ; i ++ ) {
371
+ info = tabsInfo [ i ] ;
372
+ if ( ! isDisabled ( info ) ) {
373
+ return info ;
374
+ }
375
+ }
376
+ if ( wrap ) {
377
+ for ( let i = 0 ; i < index ; i ++ ) {
378
+ info = tabsInfo [ i ] ;
379
+ if ( ! isDisabled ( info ) ) {
380
+ return info ;
381
+ }
382
+ }
383
+ }
384
+ return ;
385
+ } ;
386
+
387
+ // Find an enabled tab before the index
388
+ export const findPrevEnabledTab = (
389
+ tabsInfo : TabInfo [ ] ,
390
+ index : number ,
391
+ wrap ?: boolean
392
+ ) => {
393
+ let info ;
394
+ for ( let i = Math . min ( tabsInfo . length , index ) - 1 ; i >= 0 ; i -- ) {
395
+ info = tabsInfo [ i ] ;
396
+ if ( ! isDisabled ( info ) ) {
397
+ return info ;
398
+ }
399
+ }
400
+ if ( wrap ) {
401
+ for ( let i = tabsInfo . length - 1 ; i > index ; i -- ) {
402
+ info = tabsInfo [ i ] ;
403
+ if ( ! isDisabled ( info ) ) {
404
+ return info ;
405
+ }
406
+ }
407
+ }
408
+ return ;
409
+ } ;
410
+
411
+ export const isDisabled = ( tabInfo : TabInfo ) => tabInfo . tabProps . disabled ;
0 commit comments