@@ -64,49 +64,6 @@ export type TabInfo = {
6464 tabProps : TabProps ;
6565 panelProps : TabPanelProps ;
6666} ;
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 ) ;
11067
11168// This function reads the children, assigns indexes and creates a
11269// standard structure. It must take care to retain the props objects
@@ -119,7 +76,7 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
11976 let tabListElement : JSXNode | undefined ;
12077 const tabComponents : JSXNode [ ] = [ ] ;
12178 const panelComponents : JSXNode [ ] = [ ] ;
122- const tabsInfo : TabInfo [ ] = [ ] ;
79+ const tabInfoList : TabInfo [ ] = [ ] ;
12380 let panelIndex = 0 ;
12481 let selectedIndex ;
12582
@@ -173,7 +130,7 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
173130 child . props . _extraClass = panelClass ;
174131
175132 panelComponents . push ( child ) ;
176- tabsInfo . push ( {
133+ tabInfoList . push ( {
177134 tabId,
178135 index : panelIndex ,
179136 panelProps : child . props
@@ -195,11 +152,11 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
195152 }
196153
197154 tabComponents . forEach ( ( tab , index ) => {
198- const tabId = tabsInfo [ index ] ?. tabId ;
155+ const tabId = tabInfoList [ index ] ?. tabId ;
199156 tab . key = tabId ;
200157 tab . props . _tabId = tabId ;
201158 tab . props . _extraClass = tabClass ;
202- tabsInfo [ index ] . tabProps = tab . props ;
159+ tabInfoList [ index ] . tabProps = tab . props ;
203160 } ) ;
204161
205162 if ( tabListElement ) {
@@ -215,47 +172,18 @@ export const Tabs: FunctionComponent<TabsProps> = (props) => {
215172 }
216173
217174 return (
218- < TabsImpl tabs = { tabsInfo } { ...rest } >
175+ < TabsImpl tabInfoList = { tabInfoList } { ...rest } >
219176 { tabListElement }
220177 { panelComponents }
221178 </ TabsImpl >
222179 ) ;
223180} ;
224181
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 [ ] } ) => {
255183 const {
256184 // We take these out of the props for the DOM element but we must refer
257185 // to them as e.g. props.tabs for reactivity
258- tabs : _0 ,
186+ tabInfoList : _0 ,
259187 behavior = 'manual' ,
260188 selectedTabId : _1 ,
261189 selectedIndex : _2 ,
@@ -278,35 +206,49 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
278206 useTask$ ( function syncTabsTask ( { track } ) {
279207 // Possible optimizer bug: tracking only works with props.tabs
280208 // TODO: Write a test in Qwik optimizer to prove this bug
281- const tabs = track ( ( ) => props . tabs ) ;
209+ const tabInfoList = track ( ( ) => props . tabInfoList ) ;
282210 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+ ) ;
284218 } ) ;
285219 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+ } ) ;
288224 } ) ;
289225 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+ } ) ;
292230 if ( typeof selectedIndexSig . value !== 'undefined' ) {
293231 onSelectedIndexChange$ ?.( selectedIndexSig . value ) ;
294232 }
295233 } ) ;
296234 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+ } ) ;
299239 } ) ;
300240 useTask$ ( function syncSelectedTabIdSigTask ( { track } ) {
301- let tabId = track ( ( ) => selectedTabIdSig . value ) ;
241+ let updatedTabId = track ( ( ) => selectedTabIdSig . value ) ;
302242 // 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 ) ;
305245 if ( tab ) {
306- tabId = tab . tabId ;
246+ updatedTabId = tab . tabId ;
307247 }
308248 }
309- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { tabId } ) ;
249+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
250+ tabIdToSelect : updatedTabId
251+ } ) ;
310252 if ( typeof selectedTabIdSig . value !== 'undefined' ) {
311253 onSelectedTabIdChange$ ?.( selectedTabIdSig . value ) ;
312254 }
@@ -319,7 +261,9 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
319261 } ) ;
320262
321263 const selectTab$ = $ ( ( tabId : string ) => {
322- updateSignals ( props . tabs , selectedIndexSig , selectedTabIdSig , { tabId } ) ;
264+ syncSelectedStateSignals ( props . tabInfoList , selectedIndexSig , selectedTabIdSig , {
265+ tabIdToSelect : tabId
266+ } ) ;
323267 } ) ;
324268
325269 const selectIfAutomatic$ = $ ( ( tabId : string ) => {
@@ -331,28 +275,28 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
331275 const onTabKeyDown$ = $ ( ( key : KeyCode , currentTabId : string ) => {
332276 const tabsRootElement = ref . value ;
333277
334- const currentFocusedTabIndex = props . tabs . findIndex (
278+ const currentFocusedTabIndex = props . tabInfoList . findIndex (
335279 ( tabData ) => tabData . tabId === currentTabId
336280 ) ;
337281
338- let tab ;
282+ let tabInfo ;
339283 if (
340284 ( ! vertical && key === KeyCode . ArrowRight ) ||
341285 ( vertical && key === KeyCode . ArrowDown )
342286 ) {
343- tab = findEnabledTab ( props . tabs , currentFocusedTabIndex + 1 , true ) ;
287+ tabInfo = findNextEnabledTab ( props . tabInfoList , currentFocusedTabIndex + 1 , true ) ;
344288 } else if (
345289 ( ! vertical && key === KeyCode . ArrowLeft ) ||
346290 ( vertical && key === KeyCode . ArrowUp )
347291 ) {
348- tab = findPrevEnabledTab ( props . tabs , currentFocusedTabIndex , true ) ;
292+ tabInfo = findPrevEnabledTab ( props . tabInfoList , currentFocusedTabIndex , true ) ;
349293 } else if ( key === KeyCode . Home || key === KeyCode . PageUp ) {
350- tab = findEnabledTab ( props . tabs , 0 ) ;
294+ tabInfo = findNextEnabledTab ( props . tabInfoList , 0 ) ;
351295 } else if ( key === KeyCode . End || key === KeyCode . PageDown ) {
352- tab = findPrevEnabledTab ( props . tabs , props . tabs . length ) ;
296+ tabInfo = findPrevEnabledTab ( props . tabInfoList , props . tabInfoList . length ) ;
353297 }
354- if ( tab ) {
355- focusOnTab ( tab . index ) ;
298+ if ( tabInfo ) {
299+ focusOnTab ( tabInfo . index ) ;
356300 }
357301
358302 function focusOnTab ( index : number ) {
@@ -379,3 +323,89 @@ export const TabsImpl = component$((props: TabsProps & { tabs: TabInfo[] }) => {
379323 </ div >
380324 ) ;
381325} ) ;
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