@@ -15,7 +15,7 @@ import { NameGenerator } from "comps/utils";
1515import { ScrollBar , Section , sectionNames } from "lowcoder-design" ;
1616import { HintPlaceHolder } from "lowcoder-design" ;
1717import _ from "lodash" ;
18- import React , { useCallback , useContext , useEffect } from "react" ;
18+ import React , { useContext , useMemo } from "react" ;
1919import styled , { css } from "styled-components" ;
2020import { IContainer } from "../containerBase/iContainer" ;
2121import { SimpleContainerComp } from "../containerBase/simpleContainerComp" ;
@@ -34,7 +34,7 @@ import { EditorContext } from "comps/editorState";
3434import { checkIsMobile } from "util/commonUtils" ;
3535import { messageInstance } from "lowcoder-design/src/components/GlobalInstances" ;
3636import { BoolControl } from "comps/controls/boolControl" ;
37- import { PositionControl } from "comps/controls/dropdownControl" ;
37+ import { PositionControl , dropdownControl } from "comps/controls/dropdownControl" ;
3838import { SliderControl } from "@lowcoder-ee/comps/controls/sliderControl" ;
3939import { getBackgroundStyle } from "@lowcoder-ee/util/styleUtils" ;
4040
@@ -46,6 +46,14 @@ const EVENT_OPTIONS = [
4646 } ,
4747] as const ;
4848
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+
4957const childrenMap = {
5058 tabs : TabsOptionControl ,
5159 selectedTabKey : stringExposingStateControl ( "key" , "Tab1" ) ,
@@ -61,7 +69,7 @@ const childrenMap = {
6169 onEvent : eventHandlerControl ( EVENT_OPTIONS ) ,
6270 disabled : BoolCodeControl ,
6371 showHeader : withDefault ( BoolControl , true ) ,
64- destroyInactiveTab : withDefault ( BoolControl , false ) ,
72+ tabBehavior : withDefault ( TabBehaviorControl , "lazy" ) ,
6573 style : styleControl ( TabContainerStyle , 'style' ) ,
6674 headerStyle : styleControl ( ContainerHeaderStyle , 'headerStyle' ) ,
6775 bodyStyle : styleControl ( TabBodyStyle , 'bodyStyle' ) ,
@@ -72,7 +80,7 @@ const childrenMap = {
7280
7381type ViewProps = RecordConstructorToView < typeof childrenMap > ;
7482type TabbedContainerProps = ViewProps & { dispatch : DispatchType } ;
75-
83+
7684const getStyle = (
7785 style : TabContainerStyleType ,
7886 headerStyle : ContainerHeaderStyleType ,
@@ -138,13 +146,14 @@ const getStyle = (
138146 ` ;
139147} ;
140148
141- const StyledTabs = styled ( Tabs ) < {
149+ const StyledTabs = styled ( Tabs ) < {
142150 $style : TabContainerStyleType ;
143151 $headerStyle : ContainerHeaderStyleType ;
144152 $bodyStyle : TabBodyStyleType ;
145- $isMobile ?: boolean ;
153+ $isMobile ?: boolean ;
146154 $showHeader ?: boolean ;
147- $animationStyle :AnimationStyleType
155+ $animationStyle :AnimationStyleType ;
156+ $isDestroyPane ?: boolean ;
148157} > `
149158 &.ant-tabs {
150159 height: 100%;
@@ -157,13 +166,11 @@ const StyledTabs = styled(Tabs)<{
157166
158167 .ant-tabs-content {
159168 height: 100%;
160- // margin-top: -16px;
161169 }
162170
163171 .ant-tabs-nav {
164172 display: ${ ( props ) => ( props . $showHeader ? "block" : "none" ) } ;
165173 padding: 0 ${ ( props ) => ( props . $isMobile ? 16 : 24 ) } px;
166- // background: white;
167174 margin: 0px;
168175 }
169176
@@ -175,16 +182,71 @@ const StyledTabs = styled(Tabs)<{
175182 margin-right: -24px;
176183 }
177184
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+ ` }
183201` ;
184202
185203const 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+
186232 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 >
188250 ) ;
189251} ;
190252
@@ -197,27 +259,13 @@ const TabbedContainer = (props: TabbedContainerProps) => {
197259 headerStyle,
198260 bodyStyle,
199261 horizontalGridCells,
200- destroyInactiveTab ,
262+ tabBehavior ,
201263 } = props ;
202264
203265 const visibleTabs = tabs . filter ( ( tab ) => ! tab . hidden ) ;
204266 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+
221269
222270 const editorState = useContext ( EditorContext ) ;
223271 const maxWidth = editorState . getAppSettings ( ) . maxWidth ;
@@ -228,73 +276,69 @@ const TabbedContainer = (props: TabbedContainerProps) => {
228276 const tabItems = visibleTabs . map ( ( tab ) => {
229277 const id = String ( tab . id ) ;
230278 const childDispatch = wrapDispatch ( wrapDispatch ( dispatch , "containers" ) , id ) ;
231- const containerProps = containers [ id ] . children ;
279+ const containerChildren = containers [ id ] . children ;
232280 const hasIcon = tab . icon . props . value ;
281+
233282 const label = (
234283 < >
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 > }
238285 { 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 > }
242287 </ >
243288 ) ;
289+
290+ const forceRender = tabBehavior === "keep-alive" ;
291+
244292 return {
245293 label,
246- key : tab . key ,
247- forceRender : ! destroyInactiveTab ,
248- destroyInactiveTab : destroyInactiveTab ,
294+ key : tab . key ,
295+ forceRender,
249296 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+ } ) ;
266311
267312 return (
268313 < 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 >
294339 ) ;
295340} ;
296341
297-
298342export const TabbedContainerBaseComp = ( function ( ) {
299343 return new UICompBuilder ( childrenMap , ( props , dispatch ) => {
300344 return (
@@ -313,14 +357,32 @@ export const TabbedContainerBaseComp = (function () {
313357 } ) }
314358 { children . selectedTabKey . propertyView ( { label : trans ( "prop.defaultValue" ) } ) }
315359 </ Section >
316-
360+
317361 { [ "logic" , "both" ] . includes ( useContext ( EditorContext ) . editorModeStatus ) && (
318362 < Section name = { sectionNames . interaction } >
319363 { children . onEvent . getPropertyView ( ) }
320364 { disabledPropertyView ( children ) }
321365 { hiddenPropertyView ( children ) }
322366 { 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+ } ) }
324386 </ Section >
325387 ) }
326388
@@ -371,21 +433,18 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
371433 const actions : CompAction [ ] = [ ] ;
372434 Object . keys ( containers ) . forEach ( ( id ) => {
373435 if ( ! ids . has ( id ) ) {
374- // log.debug("syncContainers delete. ids=", ids, " id=", id);
375436 actions . push ( wrapChildAction ( "containers" , wrapChildAction ( id , deleteCompAction ( ) ) ) ) ;
376437 }
377438 } ) ;
378439 // new
379440 ids . forEach ( ( id ) => {
380441 if ( ! containers . hasOwnProperty ( id ) ) {
381- // log.debug("syncContainers new containers: ", containers, " id: ", id);
382442 actions . push (
383443 wrapChildAction ( "containers" , addMapChildAction ( id , { layout : { } , items : { } } ) )
384444 ) ;
385445 }
386446 } ) ;
387447
388- // log.debug("syncContainers. actions: ", actions);
389448 let instance = this ;
390449 actions . forEach ( ( action ) => {
391450 instance = instance . reduce ( action ) ;
@@ -414,13 +473,12 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414473 return this ;
415474 }
416475 }
417- // log.debug("before super reduce. action: ", action);
476+
418477 let newInstance = super . reduce ( action ) ;
419478 if ( action . type === CompActionTypes . UPDATE_NODES_V2 ) {
420479 // Need eval to get the value in StringControl
421480 newInstance = newInstance . syncContainers ( ) ;
422481 }
423- // log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424482 return newInstance ;
425483 }
426484
@@ -464,12 +522,9 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464522 override autoHeight ( ) : boolean {
465523 return this . children . autoHeight . getView ( ) ;
466524 }
467-
468-
469525}
470526
471527export const TabbedContainerComp = withExposingConfigs ( TabbedContainerImplComp , [
472528 new NameConfig ( "selectedTabKey" , trans ( "tabbedContainer.selectedTabKeyDesc" ) ) ,
473529 NameConfigHidden ,
474530] ) ;
475-
0 commit comments