@@ -8,21 +8,29 @@ import React, {
8
8
import { TabModel } from "src/lib/tabs-ui/tabs-ui.types.ts" ;
9
9
import { removeItem } from "src/utils/array-utils.ts" ;
10
10
11
+ type StateListener = ( state : State , partialState : Partial < State > ) => void ;
12
+
13
+ type Disposer = ( ) => void ;
14
+
11
15
export type TabsApi = {
12
- setTabs : ( tabs : TabModel [ ] , runHandlers ?: boolean ) => void ;
13
- setActiveTabId : ( id : string | undefined , runHandlers ?: boolean ) => void ;
16
+ listeners : Set < StateListener > ;
17
+ subscribe : ( cb : StateListener ) => Disposer ;
18
+ setTabs : ( tabs : TabModel [ ] , emitEvents ?: boolean ) => void ;
19
+ setActiveTabId : ( id : string | undefined , emitEvents ?: boolean ) => void ;
14
20
getState : ( ) => State ;
15
21
getActiveTab : ( ) => TabModel | undefined ;
16
22
setState : (
17
23
state : Partial < State > | { ( prevState : State ) : Partial < State > } ,
18
- runHandlers ?: boolean ,
24
+ emitEvents ?: boolean ,
19
25
) => void ;
20
26
forceUpdate : ( ) => void ;
21
27
closeTab : ( tab : TabModel ) => void ;
22
28
registerChildTabsApi : ( childTabsApi : TabsApi ) => void ;
23
29
unregisterChildTabsApi : ( ) => void ;
24
30
registerParentTabsApi : ( parentTabsApi : TabsApi ) => void ;
25
31
unregisterParentTabsApi : ( ) => void ;
32
+ setStartPinnedTabs : ( ids : string [ ] , emitEvents : boolean ) => void ;
33
+ setEndPinnedTabs : ( ids : string [ ] , emitEvents : boolean ) => void ;
26
34
} ;
27
35
28
36
export type TabsProps = {
@@ -95,33 +103,28 @@ const useActive = (apiRef: MutableRefObject<TabsApi>, props: TabsProps) => {
95
103
const { hasControlledActiveTabId, activeTabId } = props ;
96
104
apiRef . current [ "setActiveTabId" ] = (
97
105
id : string | undefined ,
98
- runHandlers = true ,
106
+ emitEvents = true ,
99
107
) => {
100
108
apiRef . current . setState (
101
109
{
102
110
activeTabId : id ,
103
111
} ,
104
- runHandlers ,
112
+ emitEvents ,
105
113
) ;
106
114
apiRef . current . forceUpdate ( ) ;
107
115
} ;
108
116
apiRef . current [ "getActiveTab" ] = ( ) => {
109
117
const { tabs, activeTabId } = apiRef . current . getState ( ) ;
110
118
return tabs . find ( ( tab ) => tab . id === activeTabId ) ;
111
119
} ;
112
- useEffect ( ( ) => {
113
- if ( hasControlledActiveTabId ) {
114
- apiRef . current . setActiveTabId ( activeTabId , false ) ;
115
- }
116
- } , [ hasControlledActiveTabId , activeTabId , apiRef ] ) ;
120
+ const state = apiRef . current . getState ( ) ;
121
+ if ( hasControlledActiveTabId && state . activeTabId !== activeTabId ) {
122
+ apiRef . current . setActiveTabId ( activeTabId , false ) ;
123
+ }
117
124
} ;
118
125
119
126
const useTabsState = ( apiRef : MutableRefObject < TabsApi > , props : TabsProps ) => {
120
127
const {
121
- onTabsChange,
122
- onActiveTabIdChange,
123
- onStartPinnedTabsChange,
124
- onEndPinnedTabsChange,
125
128
initialTabs = [ ] ,
126
129
initialActiveTabId,
127
130
initialStartPinnedTabs = [ ] ,
@@ -143,35 +146,23 @@ const useTabsState = (apiRef: MutableRefObject<TabsApi>, props: TabsProps) => {
143
146
144
147
apiRef . current [ "setState" ] = (
145
148
stateOrFn : Partial < State > | { ( state : State ) : Partial < State > } ,
146
- runHandlers = true ,
149
+ emitEvents = true ,
147
150
) => {
148
151
const newState =
149
152
typeof stateOrFn === "function" ? stateOrFn ( stateRef . current ) : stateOrFn ;
150
153
151
- const subStateKeys : ( keyof State ) [ ] = Object . keys (
152
- newState ,
153
- ) as unknown as ( keyof State ) [ ] ;
154
-
155
- if ( runHandlers ) {
156
- subStateKeys . forEach ( ( subStateKey ) => {
157
- const subState = newState [ subStateKey ] ;
158
- const subStateChangeHandler = {
159
- tabs : onTabsChange ,
160
- activeTabId : onActiveTabIdChange ,
161
- startPinnedTabs : onStartPinnedTabsChange ,
162
- endPinnedTabs : onEndPinnedTabsChange ,
163
- childTabsApi : ( ) => { } ,
164
- parentTabsApi : ( ) => { } ,
165
- } [ subStateKey ] ;
166
- // @ts -ignore
167
- subStateChangeHandler ?.( subState ) ;
168
- } ) ;
169
- }
170
-
171
- stateRef . current = {
154
+ const mergedNewState = {
172
155
...stateRef . current ,
173
156
...newState ,
174
157
} ;
158
+
159
+ if ( emitEvents ) {
160
+ [ ...apiRef . current . listeners ] . forEach ( ( listener ) => {
161
+ listener ( mergedNewState , newState ) ;
162
+ } ) ;
163
+ }
164
+
165
+ stateRef . current = mergedNewState ;
175
166
} ;
176
167
} ;
177
168
@@ -181,42 +172,46 @@ const usePinning = (apiRef: MutableRefObject<TabsApi>, props: TabsProps) => {
181
172
endPinnedTabs : endPinnedTabsProp ,
182
173
} = props ;
183
174
const setStartPinnedTabs = useCallback (
184
- ( ids : string [ ] , runHandlers = true ) => {
175
+ ( ids : string [ ] , emitEvents = true ) => {
185
176
apiRef . current . setState (
186
177
{
187
178
startPinnedTabs : ids ,
188
179
} ,
189
- runHandlers ,
180
+ emitEvents ,
190
181
) ;
191
182
apiRef . current . forceUpdate ( ) ;
192
183
} ,
193
184
[ apiRef ] ,
194
185
) ;
195
186
196
187
const setEndPinnedTabs = useCallback (
197
- ( ids : string [ ] , runHandlers = true ) => {
188
+ ( ids : string [ ] , emitEvents = true ) => {
198
189
apiRef . current . setState (
199
190
{
200
191
endPinnedTabs : ids ,
201
192
} ,
202
- runHandlers ,
193
+ emitEvents ,
203
194
) ;
204
195
apiRef . current . forceUpdate ( ) ;
205
196
} ,
206
197
[ apiRef ] ,
207
198
) ;
199
+ apiRef . current [ "setStartPinnedTabs" ] = setStartPinnedTabs ;
200
+ apiRef . current [ "setEndPinnedTabs" ] = setEndPinnedTabs ;
201
+ const state = apiRef . current . getState ( ) ;
208
202
209
- useEffect ( ( ) => {
210
- if ( startPinnedTabsProp ) {
211
- setStartPinnedTabs ( startPinnedTabsProp , false ) ;
212
- }
213
- } , [ startPinnedTabsProp , setStartPinnedTabs ] ) ;
214
-
215
- useEffect ( ( ) => {
216
- if ( endPinnedTabsProp ) {
217
- setEndPinnedTabs ( endPinnedTabsProp , false ) ;
218
- }
219
- } , [ endPinnedTabsProp , setEndPinnedTabs ] ) ;
203
+ if (
204
+ startPinnedTabsProp &&
205
+ state . startPinnedTabs . join ( "" ) != startPinnedTabsProp . join ( "" )
206
+ ) {
207
+ apiRef . current . setStartPinnedTabs ( startPinnedTabsProp , false ) ;
208
+ }
209
+ if (
210
+ endPinnedTabsProp &&
211
+ state . endPinnedTabs . join ( "" ) != endPinnedTabsProp . join ( "" )
212
+ ) {
213
+ apiRef . current . setEndPinnedTabs ( endPinnedTabsProp , false ) ;
214
+ }
220
215
} ;
221
216
222
217
const useChildTabsApi = ( apiRef : MutableRefObject < TabsApi > ) => {
@@ -258,32 +253,79 @@ const useChildTabsApi = (apiRef: MutableRefObject<TabsApi>) => {
258
253
} ;
259
254
260
255
const useTabModels = ( apiRef : MutableRefObject < TabsApi > , props : TabsProps ) => {
261
- const { tabs : tabsProp } = props ;
262
- apiRef . current [ "setTabs" ] = ( tabs : TabModel [ ] , runHandlers = true ) => {
256
+ const { tabs : tabsFromProps } = props ;
257
+
258
+ apiRef . current [ "setTabs" ] = ( tabs : TabModel [ ] , emitEvents = true ) => {
263
259
apiRef . current . setState (
264
260
{
265
261
tabs,
266
262
} ,
267
- runHandlers ,
263
+ emitEvents ,
268
264
) ;
269
265
apiRef . current . forceUpdate ( ) ;
270
266
} ;
271
267
272
- useEffect ( ( ) => {
273
- if ( tabsProp ) {
274
- apiRef . current . setTabs ( tabsProp , false ) ;
275
- }
276
- } , [ tabsProp , apiRef ] ) ;
268
+ const { tabs : prevTabs } = apiRef . current . getState ( ) ;
269
+
270
+ const hashTabs = ( tabs : TabModel [ ] ) => {
271
+ return tabs . map ( ( tab ) => tab . id ) . join ( "" ) ;
272
+ } ;
273
+
274
+ if ( tabsFromProps && hashTabs ( prevTabs ) != hashTabs ( tabsFromProps ) ) {
275
+ apiRef . current . setTabs ( tabsFromProps , false ) ;
276
+ }
277
277
} ;
278
278
279
279
export const useTabs = (
280
280
apiRef : MutableRefObject < TabsApi > ,
281
281
props : TabsProps ,
282
282
) => {
283
+ const {
284
+ onTabsChange,
285
+ onActiveTabIdChange,
286
+ onStartPinnedTabsChange,
287
+ onEndPinnedTabsChange,
288
+ } = props ;
283
289
apiRef . current [ "forceUpdate" ] = useForceRerender ( ) ;
284
290
285
- useTabModels ( apiRef , props ) ;
291
+ if ( ! apiRef . current . listeners ) {
292
+ apiRef . current . listeners = new Set < StateListener > ( ) ;
293
+ }
294
+
295
+ apiRef . current [ "subscribe" ] = ( cb : StateListener ) => {
296
+ apiRef . current . listeners . add ( cb ) ;
297
+ return ( ) => {
298
+ apiRef . current . listeners . delete ( cb ) ;
299
+ } ;
300
+ } ;
301
+
302
+ useEffect ( ( ) => {
303
+ const handlersMap = {
304
+ tabs : onTabsChange ,
305
+ activeTabId : onActiveTabIdChange ,
306
+ startPinnedTabs : onStartPinnedTabsChange ,
307
+ endPinnedTabs : onEndPinnedTabsChange ,
308
+ } ;
309
+ return apiRef . current . subscribe ( ( _ , partialState ) => {
310
+ Object . keys ( partialState ) . forEach ( ( subStateKey ) => {
311
+ const subState = partialState [ subStateKey as keyof State ] ;
312
+ // @ts -ignore
313
+ const subStateChangeHandler = handlersMap [ subStateKey ] ;
314
+ // @ts -ignore
315
+ subStateChangeHandler ?.( subState ) ;
316
+ } ) ;
317
+ } ) ;
318
+ } , [
319
+ apiRef ,
320
+ onTabsChange ,
321
+ onActiveTabIdChange ,
322
+ onStartPinnedTabsChange ,
323
+ onEndPinnedTabsChange ,
324
+ ] ) ;
325
+
286
326
useTabsState ( apiRef , props ) ;
327
+ useTabModels ( apiRef , props ) ;
328
+
287
329
useClosing ( apiRef ) ;
288
330
usePinning ( apiRef , props ) ;
289
331
useActive ( apiRef , props ) ;
0 commit comments