@@ -48,38 +48,50 @@ export type Behavior = 'automatic' | 'manual';
48
48
49
49
interface TabsContext {
50
50
selectedIndex : Signal < number > ;
51
- getNextTabIndex : QRL < ( ) => number > ;
52
- getNextPanelIndex : QRL < ( ) => number > ;
51
+ selectedTabId : Signal < string > ;
52
+ registerTab : QRL < ( tabId : string ) => void > ;
53
53
behavior : Behavior ;
54
+ reIndexTabs : Signal < boolean > ;
55
+ setTabIndex : QRL < ( tabId : string , index : number ) => void > ;
54
56
}
55
57
56
58
export const tabsContextId = createContextId < TabsContext > ( 'qui--tabList' ) ;
57
59
58
60
export interface TabsProps {
59
61
behavior ?: Behavior ;
60
62
class ?: string ;
63
+ selectedIndex ?: number ;
64
+ }
65
+
66
+ export interface TabIndexMap {
67
+ [ key : string ] : number | undefined ;
61
68
}
62
69
63
70
export const Tabs = component$ ( ( props : TabsProps ) => {
64
71
const behavior = props . behavior ?? 'manual' ;
65
- const lastTabIndex = useSignal ( 0 ) ;
66
- const lastPanelIndex = useSignal ( 0 ) ;
72
+ const selectedIndex = useSignal ( props . selectedIndex || 0 ) ;
73
+ const reIndexTabs = useSignal ( false ) ;
74
+
75
+ const tabsRegistry : TabIndexMap = { } ;
67
76
68
- const getNextTabIndex = $ ( ( ) => {
69
- return lastTabIndex . value ++ ;
77
+ const setTabIndex = $ ( ( tabId : string , index : number ) => {
78
+ tabsRegistry [ tabId ] = index ;
70
79
} ) ;
71
80
72
- const getNextPanelIndex = $ ( ( ) => {
73
- return lastPanelIndex . value ++ ;
81
+ const registerTab = $ ( ( tabId : string ) => {
82
+ tabsRegistry [ tabId ] = undefined ;
83
+ reIndexTabs . value = true ;
74
84
} ) ;
75
85
76
- const selected = useSignal ( 0 ) ;
86
+ const selectedTabId = useSignal ( '' ) ;
77
87
78
88
const contextService : TabsContext = {
79
- selectedIndex : selected ,
80
- getNextTabIndex ,
81
- getNextPanelIndex ,
89
+ selectedIndex,
90
+ registerTab ,
91
+ setTabIndex ,
82
92
behavior,
93
+ reIndexTabs,
94
+ selectedTabId,
83
95
} ;
84
96
85
97
const tabsInitialized = useSignal ( false ) ;
@@ -109,8 +121,25 @@ interface TabListProps {
109
121
// List of tabs that can be clicked to show different content.
110
122
export const TabList = component$ ( ( props : TabListProps ) => {
111
123
const { labelledBy, ...rest } = props ;
124
+ const contextService = useContext ( tabsContextId ) ;
125
+ const ref = useSignal < Element | undefined > ( ) ;
126
+
127
+ useVisibleTask$ ( ( { track } ) => {
128
+ track ( ( ) => contextService . reIndexTabs . value ) ;
129
+
130
+ if ( ref . value ) {
131
+ ref . value . querySelectorAll ( '[role="tab"]' ) . forEach ( ( tab , index ) => {
132
+ const tabId = tab . getAttribute ( 'id' ) ;
133
+
134
+ if ( tabId ) {
135
+ contextService . setTabIndex ( tabId , index ) ;
136
+ }
137
+ } ) ;
138
+ }
139
+ } ) ;
140
+
112
141
return (
113
- < div role = "tablist" aria-labelledby = { labelledBy } { ...rest } >
142
+ < div ref = { ref } role = "tablist" aria-labelledby = { labelledBy } { ...rest } >
114
143
< Slot />
115
144
</ div >
116
145
) ;
@@ -124,38 +153,31 @@ interface TabProps {
124
153
125
154
// Tab button inside of a tab list
126
155
export const Tab = component$ (
127
- ( { selectedClassName, onClick, ... props } : TabProps ) => {
156
+ ( { selectedClassName, onClick, class : classString } : TabProps ) => {
128
157
const contextService = useContext ( tabsContextId ) ;
129
158
const thisTabIndex = useSignal ( 0 ) ;
130
159
131
- useVisibleTask$ ( async ( ) => {
132
- thisTabIndex . value = await contextService . getNextTabIndex ( ) ;
133
- console . log ( 'useVisibleTask$' , thisTabIndex . value ) ;
134
- } ) ;
135
-
136
- // TODO: Ask Manu about this 😊
137
- const isSelected = ( ) =>
138
- thisTabIndex . value === contextService . selectedIndex . value ;
160
+ const isSelected = ( ) => forTab === contextService . selectedTabId . value ;
139
161
140
162
const selectIfAutomatic = $ ( ( ) => {
141
163
if ( contextService . behavior === 'automatic' ) {
142
164
contextService . selectedIndex . value = thisTabIndex . value ;
143
165
}
144
166
} ) ;
145
167
146
- const uniqueId = useId ( ) ;
168
+ const id = useId ( ) ;
147
169
148
170
return (
149
171
< button
150
- id = { uniqueId }
172
+ id = { id }
151
173
type = "button"
152
174
role = "tab"
153
175
onFocus$ = { selectIfAutomatic }
154
176
onMouseEnter$ = { selectIfAutomatic }
155
177
aria-selected = { isSelected ( ) }
156
178
aria-controls = { `tabpanel-${ thisTabIndex . value } ` }
157
179
class = { `${ isSelected ( ) ? `selected ${ selectedClassName } ` : '' } ${
158
- props . class ? ` ${ props . class } ` : ''
180
+ classString ? ` ${ classString } ` : ''
159
181
} `}
160
182
onClick$ = { $ ( ( ) => {
161
183
contextService . selectedIndex . value = thisTabIndex . value ;
@@ -182,7 +204,9 @@ export const TabPanel = component$(({ ...props }: TabPanelProps) => {
182
204
const isSelected = ( ) =>
183
205
thisPanelIndex . value === contextService . selectedIndex . value ;
184
206
useVisibleTask$ ( async ( ) => {
185
- thisPanelIndex . value = await contextService . getNextPanelIndex ( ) ;
207
+ setTimeout ( async ( ) => {
208
+ thisPanelIndex . value = await contextService . getNextPanelIndex ( ) ;
209
+ } ) ;
186
210
} ) ;
187
211
const uniqueId = useId ( ) ;
188
212
return (
0 commit comments