@@ -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 , { useContext , useEffect , useState } 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" ;
@@ -47,10 +47,9 @@ const EVENT_OPTIONS = [
4747] as const ;
4848
4949const TAB_BEHAVIOR_OPTIONS = [
50- { label : "Lazy Loading" , value : "lazy" } ,
51- { label : "Remember State" , value : "remember" } ,
52- { label : "Destroy Inactive" , value : "destroy" } ,
53- { label : "Keep Alive (render all)" , value : "keep-alive" } ,
50+ { label : trans ( "tabbedContainer.tabBehaviorLazy" ) , value : "lazy" } ,
51+ { label : trans ( "tabbedContainer.tabBehaviorKeepAlive" ) , value : "keep-alive" } ,
52+ { label : trans ( "tabbedContainer.tabBehaviorDestroy" ) , value : "destroy" } ,
5453] as const ;
5554
5655const TabBehaviorControl = dropdownControl ( TAB_BEHAVIOR_OPTIONS , "lazy" ) ;
@@ -153,7 +152,8 @@ const StyledTabs = styled(Tabs)<{
153152 $bodyStyle : TabBodyStyleType ;
154153 $isMobile ?: boolean ;
155154 $showHeader ?: boolean ;
156- $animationStyle :AnimationStyleType
155+ $animationStyle :AnimationStyleType ;
156+ $isDestroyPane ?: boolean ;
157157} > `
158158 &.ant-tabs {
159159 height: 100%;
@@ -166,7 +166,6 @@ const StyledTabs = styled(Tabs)<{
166166
167167 .ant-tabs-content {
168168 height: 100%;
169-
170169 }
171170
172171 .ant-tabs-nav {
@@ -183,16 +182,71 @@ const StyledTabs = styled(Tabs)<{
183182 margin-right: -24px;
184183 }
185184
186- ${ ( props ) => props . $style && getStyle (
187- props . $style ,
188- props . $headerStyle ,
189- props . $bodyStyle ,
190- ) }
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+ ` }
191201` ;
192202
193203const 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+
194232 return (
195- < 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 >
196250 ) ;
197251} ;
198252
@@ -212,13 +266,6 @@ const TabbedContainer = (props: TabbedContainerProps) => {
212266 const selectedTab = visibleTabs . find ( ( tab ) => tab . key === props . selectedTabKey . value ) ;
213267 const activeKey = selectedTab ? selectedTab . key : visibleTabs . length > 0 ? visibleTabs [ 0 ] . key : undefined ;
214268
215- // Placeholder-based lazy loading — only for "lazy" mode
216- const [ loadedTabs , setLoadedTabs ] = useState < Set < string > > ( new Set ( ) ) ;
217- useEffect ( ( ) => {
218- if ( tabBehavior === "lazy" && activeKey ) {
219- setLoadedTabs ( ( prev : Set < string > ) => new Set ( [ ...prev , activeKey ] ) ) ;
220- }
221- } , [ tabBehavior , activeKey ] ) ;
222269
223270 const editorState = useContext ( EditorContext ) ;
224271 const maxWidth = editorState . getAppSettings ( ) . maxWidth ;
@@ -229,7 +276,7 @@ const TabbedContainer = (props: TabbedContainerProps) => {
229276 const tabItems = visibleTabs . map ( ( tab ) => {
230277 const id = String ( tab . id ) ;
231278 const childDispatch = wrapDispatch ( wrapDispatch ( dispatch , "containers" ) , id ) ;
232- const containerProps = containers [ id ] . children ;
279+ const containerChildren = containers [ id ] . children ;
233280 const hasIcon = tab . icon . props . value ;
234281
235282 const label = (
@@ -240,50 +287,25 @@ const TabbedContainer = (props: TabbedContainerProps) => {
240287 </ >
241288 ) ;
242289
243- // Item-level forceRender mapping
244- const forceRender : boolean = tabBehavior === "keep-alive" ;
245-
246- // Render content (placeholder only for "lazy" & not yet opened)
247- const renderTabContent = ( ) => {
248- if ( tabBehavior === "lazy" && ! loadedTabs . has ( tab . key ) ) {
249- return (
250- < div
251- style = { {
252- display : "flex" ,
253- justifyContent : "center" ,
254- alignItems : "center" ,
255- height : "200px" ,
256- color : "#999" ,
257- fontSize : "14px" ,
258- } }
259- >
260- Click to load tab content
261- </ div >
262- ) ;
263- }
264-
265- return (
266- < BackgroundColorContext . Provider value = { bodyStyle . background } >
267- < ScrollBar style = { { height : props . autoHeight ? "auto" : "100%" , margin : "0px" , padding : "0px" } } hideScrollbar = { ! props . showVerticalScrollbar } overflow = { props . autoHeight ? 'hidden' :'scroll' } >
268- < ContainerInTab
269- layout = { containerProps . layout . getView ( ) }
270- items = { gridItemCompToGridItems ( containerProps . items . getView ( ) ) }
271- horizontalGridCells = { horizontalGridCells }
272- positionParams = { containerProps . positionParams . getView ( ) }
273- dispatch = { childDispatch }
274- autoHeight = { props . autoHeight }
275- containerPadding = { [ paddingWidth , 20 ] }
276- />
277- </ ScrollBar >
278- </ BackgroundColorContext . Provider >
279- ) ;
280- } ;
290+ const forceRender = tabBehavior === "keep-alive" ;
281291
282292 return {
283293 label,
284294 key : tab . key ,
285- forceRender, // true only for keep-alive
286- children : renderTabContent ( ) ,
295+ forceRender,
296+ children : (
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+ ) ,
287309 } ;
288310 } ) ;
289311
@@ -299,13 +321,11 @@ const TabbedContainer = (props: TabbedContainerProps) => {
299321 $headerStyle = { headerStyle }
300322 $bodyStyle = { bodyStyle }
301323 $showHeader = { showHeader }
324+ $isDestroyPane = { tabBehavior === "destroy" }
302325 onChange = { ( key ) => {
303326 if ( key !== props . selectedTabKey . value ) {
304327 props . selectedTabKey . onChange ( key ) ;
305328 props . onEvent ( "change" ) ;
306- if ( tabBehavior === "lazy" ) {
307- setLoadedTabs ( ( prev : Set < string > ) => new Set ( [ ...prev , key ] ) ) ;
308- }
309329 }
310330 } }
311331 animated
@@ -344,7 +364,25 @@ export const TabbedContainerBaseComp = (function () {
344364 { disabledPropertyView ( children ) }
345365 { hiddenPropertyView ( children ) }
346366 { children . showHeader . propertyView ( { label : trans ( "tabbedContainer.showTabs" ) } ) }
347- { children . tabBehavior . propertyView ( { label : "Tab Behavior" } ) }
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+ } ) }
348386 </ Section >
349387 ) }
350388
@@ -435,6 +473,7 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
435473 return this ;
436474 }
437475 }
476+
438477 let newInstance = super . reduce ( action ) ;
439478 if ( action . type === CompActionTypes . UPDATE_NODES_V2 ) {
440479 // Need eval to get the value in StringControl
@@ -489,4 +528,3 @@ export const TabbedContainerComp = withExposingConfigs(TabbedContainerImplComp,
489528 new NameConfig ( "selectedTabKey" , trans ( "tabbedContainer.selectedTabKeyDesc" ) ) ,
490529 NameConfigHidden ,
491530] ) ;
492-
0 commit comments