@@ -15,7 +15,7 @@ import { NameGenerator } from "comps/utils";
15
15
import { ScrollBar , Section , sectionNames } from "lowcoder-design" ;
16
16
import { HintPlaceHolder } from "lowcoder-design" ;
17
17
import _ from "lodash" ;
18
- import React , { useCallback , useContext , useEffect } from "react" ;
18
+ import React , { useContext , useMemo } from "react" ;
19
19
import styled , { css } from "styled-components" ;
20
20
import { IContainer } from "../containerBase/iContainer" ;
21
21
import { SimpleContainerComp } from "../containerBase/simpleContainerComp" ;
@@ -34,7 +34,7 @@ import { EditorContext } from "comps/editorState";
34
34
import { checkIsMobile } from "util/commonUtils" ;
35
35
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances" ;
36
36
import { BoolControl } from "comps/controls/boolControl" ;
37
- import { PositionControl } from "comps/controls/dropdownControl" ;
37
+ import { PositionControl , dropdownControl } from "comps/controls/dropdownControl" ;
38
38
import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl" ;
39
39
import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils" ;
40
40
@@ -46,6 +46,14 @@ const EVENT_OPTIONS = [
46
46
} ,
47
47
] as const ;
48
48
49
+ const TAB_BEHAVIOR_OPTIONS = [
50
+ { label : trans ( "tabbedContainer.tabBehaviorLazy" ) , value : "lazy" } ,
51
+ { label : trans ( "tabbedContainer.tabBehaviorKeepAlive" ) , value : "keep-alive" } ,
52
+ { label : trans ( "tabbedContainer.tabBehaviorDestroy" ) , value : "destroy" } ,
53
+ ] as const ;
54
+
55
+ const TabBehaviorControl = dropdownControl ( TAB_BEHAVIOR_OPTIONS , "lazy" ) ;
56
+
49
57
const childrenMap = {
50
58
tabs : TabsOptionControl ,
51
59
selectedTabKey : stringExposingStateControl ( "key" , "Tab1" ) ,
@@ -61,7 +69,7 @@ const childrenMap = {
61
69
onEvent : eventHandlerControl ( EVENT_OPTIONS ) ,
62
70
disabled : BoolCodeControl ,
63
71
showHeader : withDefault ( BoolControl , true ) ,
64
- destroyInactiveTab : withDefault ( BoolControl , false ) ,
72
+ tabBehavior : withDefault ( TabBehaviorControl , "lazy" ) ,
65
73
style : styleControl ( TabContainerStyle , 'style' ) ,
66
74
headerStyle : styleControl ( ContainerHeaderStyle , 'headerStyle' ) ,
67
75
bodyStyle : styleControl ( TabBodyStyle , 'bodyStyle' ) ,
@@ -72,7 +80,7 @@ const childrenMap = {
72
80
73
81
type ViewProps = RecordConstructorToView < typeof childrenMap > ;
74
82
type TabbedContainerProps = ViewProps & { dispatch : DispatchType } ;
75
-
83
+
76
84
const getStyle = (
77
85
style : TabContainerStyleType ,
78
86
headerStyle : ContainerHeaderStyleType ,
@@ -138,13 +146,14 @@ const getStyle = (
138
146
` ;
139
147
} ;
140
148
141
- const StyledTabs = styled ( Tabs ) < {
149
+ const StyledTabs = styled ( Tabs ) < {
142
150
$style : TabContainerStyleType ;
143
151
$headerStyle : ContainerHeaderStyleType ;
144
152
$bodyStyle : TabBodyStyleType ;
145
- $isMobile ?: boolean ;
153
+ $isMobile ?: boolean ;
146
154
$showHeader ?: boolean ;
147
- $animationStyle :AnimationStyleType
155
+ $animationStyle :AnimationStyleType ;
156
+ $isDestroyPane ?: boolean ;
148
157
} > `
149
158
&.ant-tabs {
150
159
height: 100%;
@@ -157,13 +166,11 @@ const StyledTabs = styled(Tabs)<{
157
166
158
167
.ant-tabs-content {
159
168
height: 100%;
160
- // margin-top: -16px;
161
169
}
162
170
163
171
.ant-tabs-nav {
164
172
display: ${ ( props ) => ( props . $showHeader ? "block" : "none" ) } ;
165
173
padding: 0 ${ ( props ) => ( props . $isMobile ? 16 : 24 ) } px;
166
- // background: white;
167
174
margin: 0px;
168
175
}
169
176
@@ -175,16 +182,71 @@ const StyledTabs = styled(Tabs)<{
175
182
margin-right: -24px;
176
183
}
177
184
178
- ${ ( props ) => props . $style && getStyle (
179
- props . $style ,
180
- props . $headerStyle ,
181
- props . $bodyStyle ,
182
- ) }
185
+ ${ ( props ) =>
186
+ props . $style && getStyle ( props . $style , props . $headerStyle , props . $bodyStyle ) }
187
+
188
+ /* Conditional styling for all modes except Destroy Inactive Pane */
189
+ ${ ( props ) => ! props . $isDestroyPane && `
190
+ .ant-tabs-content-holder { position: relative; }
191
+
192
+ .ant-tabs-tabpane[aria-hidden="true"],
193
+ .ant-tabs-tabpane-hidden {
194
+ display: block !important;
195
+ visibility: hidden !important;
196
+ position: absolute !important;
197
+ inset: 0;
198
+ pointer-events: none;
199
+ }
200
+ ` }
183
201
` ;
184
202
185
203
const ContainerInTab = ( props : ContainerBaseProps ) => {
204
+ return < InnerGrid { ...props } emptyRows = { 15 } hintPlaceholder = { HintPlaceHolder } /> ;
205
+ } ;
206
+
207
+ type TabPaneContentProps = {
208
+ autoHeight : boolean ;
209
+ showVerticalScrollbar : boolean ;
210
+ paddingWidth : number ;
211
+ horizontalGridCells : number ;
212
+ bodyBackground : string ;
213
+ layoutView : any ;
214
+ itemsView : any ;
215
+ positionParamsView : any ;
216
+ dispatch : DispatchType ;
217
+ } ;
218
+
219
+ const TabPaneContent : React . FC < TabPaneContentProps > = ( {
220
+ autoHeight,
221
+ showVerticalScrollbar,
222
+ paddingWidth,
223
+ horizontalGridCells,
224
+ bodyBackground,
225
+ layoutView,
226
+ itemsView,
227
+ positionParamsView,
228
+ dispatch,
229
+ } ) => {
230
+ const gridItems = useMemo ( ( ) => gridItemCompToGridItems ( itemsView ) , [ itemsView ] ) ;
231
+
186
232
return (
187
- < InnerGrid { ...props } emptyRows = { 15 } hintPlaceholder = { HintPlaceHolder } />
233
+ < BackgroundColorContext . Provider value = { bodyBackground } >
234
+ < ScrollBar
235
+ style = { { height : autoHeight ? "auto" : "100%" , margin : "0px" , padding : "0px" } }
236
+ hideScrollbar = { ! showVerticalScrollbar }
237
+ overflow = { autoHeight ? "hidden" : "scroll" }
238
+ >
239
+ < ContainerInTab
240
+ layout = { layoutView }
241
+ items = { gridItems }
242
+ horizontalGridCells = { horizontalGridCells }
243
+ positionParams = { positionParamsView }
244
+ dispatch = { dispatch }
245
+ autoHeight = { autoHeight }
246
+ containerPadding = { [ paddingWidth , 20 ] }
247
+ />
248
+ </ ScrollBar >
249
+ </ BackgroundColorContext . Provider >
188
250
) ;
189
251
} ;
190
252
@@ -197,27 +259,13 @@ const TabbedContainer = (props: TabbedContainerProps) => {
197
259
headerStyle,
198
260
bodyStyle,
199
261
horizontalGridCells,
200
- destroyInactiveTab ,
262
+ tabBehavior ,
201
263
} = props ;
202
264
203
265
const visibleTabs = tabs . filter ( ( tab ) => ! tab . hidden ) ;
204
266
const selectedTab = visibleTabs . find ( ( tab ) => tab . key === props . selectedTabKey . value ) ;
205
- const activeKey = selectedTab
206
- ? selectedTab . key
207
- : visibleTabs . length > 0
208
- ? visibleTabs [ 0 ] . key
209
- : undefined ;
210
-
211
- const onTabClick = useCallback (
212
- ( key : string , event : React . KeyboardEvent < Element > | React . MouseEvent < Element , MouseEvent > ) => {
213
- // log.debug("onTabClick. event: ", event);
214
- const target = event . target ;
215
- ( target as any ) . parentNode . click
216
- ? ( target as any ) . parentNode . click ( )
217
- : ( target as any ) . parentNode . parentNode . click ( ) ;
218
- } ,
219
- [ ]
220
- ) ;
267
+ const activeKey = selectedTab ? selectedTab . key : visibleTabs . length > 0 ? visibleTabs [ 0 ] . key : undefined ;
268
+
221
269
222
270
const editorState = useContext ( EditorContext ) ;
223
271
const maxWidth = editorState . getAppSettings ( ) . maxWidth ;
@@ -228,73 +276,69 @@ const TabbedContainer = (props: TabbedContainerProps) => {
228
276
const tabItems = visibleTabs . map ( ( tab ) => {
229
277
const id = String ( tab . id ) ;
230
278
const childDispatch = wrapDispatch ( wrapDispatch ( dispatch , "containers" ) , id ) ;
231
- const containerProps = containers [ id ] . children ;
279
+ const containerChildren = containers [ id ] . children ;
232
280
const hasIcon = tab . icon . props . value ;
281
+
233
282
const label = (
234
283
< >
235
- { tab . iconPosition === "left" && hasIcon && (
236
- < span style = { { marginRight : "4px" } } > { tab . icon } </ span >
237
- ) }
284
+ { tab . iconPosition === "left" && hasIcon && < span style = { { marginRight : 4 } } > { tab . icon } </ span > }
238
285
{ tab . label }
239
- { tab . iconPosition === "right" && hasIcon && (
240
- < span style = { { marginLeft : "4px" } } > { tab . icon } </ span >
241
- ) }
286
+ { tab . iconPosition === "right" && hasIcon && < span style = { { marginLeft : 4 } } > { tab . icon } </ span > }
242
287
</ >
243
288
) ;
289
+
290
+ const forceRender = tabBehavior === "keep-alive" ;
291
+
244
292
return {
245
293
label,
246
- key : tab . key ,
247
- forceRender : ! destroyInactiveTab ,
248
- destroyInactiveTab : destroyInactiveTab ,
294
+ key : tab . key ,
295
+ forceRender,
249
296
children : (
250
- < BackgroundColorContext . Provider value = { bodyStyle . background } >
251
- < ScrollBar style = { { height : props . autoHeight ? "auto" : "100%" , margin : "0px" , padding : "0px" } } hideScrollbar = { ! props . showVerticalScrollbar } overflow = { props . autoHeight ? 'hidden' :'scroll' } >
252
- < ContainerInTab
253
- layout = { containerProps . layout . getView ( ) }
254
- items = { gridItemCompToGridItems ( containerProps . items . getView ( ) ) }
255
- horizontalGridCells = { horizontalGridCells }
256
- positionParams = { containerProps . positionParams . getView ( ) }
257
- dispatch = { childDispatch }
258
- autoHeight = { props . autoHeight }
259
- containerPadding = { [ paddingWidth , 20 ] }
260
- />
261
- </ ScrollBar >
262
- </ BackgroundColorContext . Provider >
263
- )
264
- }
265
- } )
297
+ < TabPaneContent
298
+ autoHeight = { props . autoHeight }
299
+ showVerticalScrollbar = { props . showVerticalScrollbar }
300
+ paddingWidth = { paddingWidth }
301
+ horizontalGridCells = { horizontalGridCells }
302
+ bodyBackground = { bodyStyle . background }
303
+ layoutView = { containerChildren . layout . getView ( ) }
304
+ itemsView = { containerChildren . items . getView ( ) }
305
+ positionParamsView = { containerChildren . positionParams . getView ( ) }
306
+ dispatch = { childDispatch }
307
+ />
308
+ ) ,
309
+ } ;
310
+ } ) ;
266
311
267
312
return (
268
313
< div style = { { padding : props . style . margin , height : props . autoHeight ? "auto" : "100%" } } >
269
- < BackgroundColorContext . Provider value = { headerStyle . headerBackground } >
270
- < StyledTabs
271
- $animationStyle = { props . animationStyle }
272
- tabPosition = { props . placement }
273
- activeKey = { activeKey }
274
- $style = { style }
275
- $headerStyle = { headerStyle }
276
- $bodyStyle = { bodyStyle }
277
- $showHeader = { showHeader }
278
- onChange = { ( key ) => {
279
- if ( key !== props . selectedTabKey . value ) {
280
- props . selectedTabKey . onChange ( key ) ;
281
- props . onEvent ( "change" ) ;
282
- }
283
- } }
284
- // onTabClick={onTabClick }
285
- animated
286
- $isMobile = { isMobile }
287
- items = { tabItems }
288
- tabBarGutter = { props . tabsGutter }
289
- centered = { props . tabsCentered }
290
- >
291
- </ StyledTabs >
292
- </ BackgroundColorContext . Provider >
293
- </ div >
314
+ < BackgroundColorContext . Provider value = { headerStyle . headerBackground } >
315
+ < StyledTabs
316
+ destroyOnHidden = { tabBehavior === "destroy" }
317
+ $animationStyle = { props . animationStyle }
318
+ tabPosition = { props . placement }
319
+ activeKey = { activeKey }
320
+ $style = { style }
321
+ $headerStyle = { headerStyle }
322
+ $bodyStyle = { bodyStyle }
323
+ $showHeader = { showHeader }
324
+ $isDestroyPane = { tabBehavior === "destroy" }
325
+ onChange = { ( key ) => {
326
+ if ( key !== props . selectedTabKey . value ) {
327
+ props . selectedTabKey . onChange ( key ) ;
328
+ props . onEvent ( "change" ) ;
329
+ }
330
+ } }
331
+ animated
332
+ $isMobile = { isMobile }
333
+ items = { tabItems }
334
+ tabBarGutter = { props . tabsGutter }
335
+ centered = { props . tabsCentered }
336
+ / >
337
+ </ BackgroundColorContext . Provider >
338
+ </ div >
294
339
) ;
295
340
} ;
296
341
297
-
298
342
export const TabbedContainerBaseComp = ( function ( ) {
299
343
return new UICompBuilder ( childrenMap , ( props , dispatch ) => {
300
344
return (
@@ -313,14 +357,32 @@ export const TabbedContainerBaseComp = (function () {
313
357
} ) }
314
358
{ children . selectedTabKey . propertyView ( { label : trans ( "prop.defaultValue" ) } ) }
315
359
</ Section >
316
-
360
+
317
361
{ [ "logic" , "both" ] . includes ( useContext ( EditorContext ) . editorModeStatus ) && (
318
362
< Section name = { sectionNames . interaction } >
319
363
{ children . onEvent . getPropertyView ( ) }
320
364
{ disabledPropertyView ( children ) }
321
365
{ hiddenPropertyView ( children ) }
322
366
{ children . showHeader . propertyView ( { label : trans ( "tabbedContainer.showTabs" ) } ) }
323
- { children . destroyInactiveTab . propertyView ( { label : trans ( "tabbedContainer.destroyInactiveTab" ) } ) }
367
+ { children . tabBehavior . propertyView ( {
368
+ label : trans ( "tabbedContainer.tabBehavior" ) ,
369
+ tooltip : (
370
+ < div style = { { display : "flex" , flexDirection : "column" , gap : 6 } } >
371
+ < div >
372
+ < b > { trans ( "tabbedContainer.tabBehaviorLazy" ) } :</ b >
373
+ { trans ( "tabbedContainer.tabBehaviorLazyTooltip" ) }
374
+ </ div >
375
+ < div >
376
+ < b > { trans ( "tabbedContainer.tabBehaviorKeepAlive" ) } :</ b >
377
+ { trans ( "tabbedContainer.tabBehaviorKeepAliveTooltip" ) }
378
+ </ div >
379
+ < div >
380
+ < b > { trans ( "tabbedContainer.tabBehaviorDestroy" ) } :</ b >
381
+ { trans ( "tabbedContainer.tabBehaviorDestroyTooltip" ) }
382
+ </ div >
383
+ </ div >
384
+ ) ,
385
+ } ) }
324
386
</ Section >
325
387
) }
326
388
@@ -371,21 +433,18 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
371
433
const actions : CompAction [ ] = [ ] ;
372
434
Object . keys ( containers ) . forEach ( ( id ) => {
373
435
if ( ! ids . has ( id ) ) {
374
- // log.debug("syncContainers delete. ids=", ids, " id=", id);
375
436
actions . push ( wrapChildAction ( "containers" , wrapChildAction ( id , deleteCompAction ( ) ) ) ) ;
376
437
}
377
438
} ) ;
378
439
// new
379
440
ids . forEach ( ( id ) => {
380
441
if ( ! containers . hasOwnProperty ( id ) ) {
381
- // log.debug("syncContainers new containers: ", containers, " id: ", id);
382
442
actions . push (
383
443
wrapChildAction ( "containers" , addMapChildAction ( id , { layout : { } , items : { } } ) )
384
444
) ;
385
445
}
386
446
} ) ;
387
447
388
- // log.debug("syncContainers. actions: ", actions);
389
448
let instance = this ;
390
449
actions . forEach ( ( action ) => {
391
450
instance = instance . reduce ( action ) ;
@@ -414,13 +473,12 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414
473
return this ;
415
474
}
416
475
}
417
- // log.debug("before super reduce. action: ", action);
476
+
418
477
let newInstance = super . reduce ( action ) ;
419
478
if ( action . type === CompActionTypes . UPDATE_NODES_V2 ) {
420
479
// Need eval to get the value in StringControl
421
480
newInstance = newInstance . syncContainers ( ) ;
422
481
}
423
- // log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424
482
return newInstance ;
425
483
}
426
484
@@ -464,12 +522,9 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464
522
override autoHeight ( ) : boolean {
465
523
return this . children . autoHeight . getView ( ) ;
466
524
}
467
-
468
-
469
525
}
470
526
471
527
export const TabbedContainerComp = withExposingConfigs ( TabbedContainerImplComp , [
472
528
new NameConfig ( "selectedTabKey" , trans ( "tabbedContainer.selectedTabKeyDesc" ) ) ,
473
529
NameConfigHidden ,
474
530
] ) ;
475
-
0 commit comments