@@ -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 = {
5058tabs :TabsOptionControl ,
5159selectedTabKey :stringExposingStateControl ( "key" , "Tab1" ) ,
@@ -61,7 +69,7 @@ const childrenMap = {
6169onEvent :eventHandlerControl ( EVENT_OPTIONS ) ,
6270disabled :BoolCodeControl ,
6371showHeader :withDefault ( BoolControl , true ) ,
64- destroyInactiveTab :withDefault ( BoolControl , false ) ,
72+ tabBehavior :withDefault ( TabBehaviorControl , "lazy" ) ,
6573style :styleControl ( TabContainerStyle , 'style' ) ,
6674headerStyle :styleControl ( ContainerHeaderStyle , 'headerStyle' ) ,
6775bodyStyle :styleControl ( TabBodyStyle , 'bodyStyle' ) ,
@@ -72,7 +80,7 @@ const childrenMap = {
7280
7381type ViewProps = RecordConstructorToView < typeof childrenMap > ;
7482type TabbedContainerProps = ViewProps & { dispatch :DispatchType } ;
75-
83+
7684const getStyle = (
7785style :TabContainerStyleType ,
7886headerStyle :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+
186232return (
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
203265const visibleTabs = tabs . filter ( ( tab ) => ! tab . hidden ) ;
204266const 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
222270const editorState = useContext ( EditorContext ) ;
223271const maxWidth = editorState . getAppSettings ( ) . maxWidth ;
@@ -228,73 +276,69 @@ const TabbedContainer = (props: TabbedContainerProps) => {
228276const tabItems = visibleTabs . map ( ( tab ) => {
229277const id = String ( tab . id ) ;
230278const childDispatch = wrapDispatch ( wrapDispatch ( dispatch , "containers" ) , id ) ;
231- const containerProps = containers [ id ] . children ;
279+ const containerChildren = containers [ id ] . children ;
232280const hasIcon = tab . icon . props . value ;
281+
233282const 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+
244292return {
245293 label,
246- key :tab . key ,
247- forceRender :! destroyInactiveTab ,
248- destroyInactiveTab :destroyInactiveTab ,
294+ key :tab . key ,
295+ forceRender,
249296children :(
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
267312return (
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 ( ) {
299343return new UICompBuilder ( childrenMap , ( props , dispatch ) => {
300344return (
@@ -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
371433const actions :CompAction [ ] = [ ] ;
372434Object . keys ( containers ) . forEach ( ( id ) => {
373435if ( ! ids . has ( id ) ) {
374- // log.debug("syncContainers delete. ids=", ids, " id=", id);
375436actions . push ( wrapChildAction ( "containers" , wrapChildAction ( id , deleteCompAction ( ) ) ) ) ;
376437}
377438} ) ;
378439// new
379440ids . forEach ( ( id ) => {
380441if ( ! containers . hasOwnProperty ( id ) ) {
381- // log.debug("syncContainers new containers: ", containers, " id: ", id);
382442actions . push (
383443wrapChildAction ( "containers" , addMapChildAction ( id , { layout :{ } , items :{ } } ) )
384444) ;
385445}
386446} ) ;
387447
388- // log.debug("syncContainers. actions: ", actions);
389448let instance = this ;
390449actions . forEach ( ( action ) => {
391450instance = instance . reduce ( action ) ;
@@ -414,13 +473,12 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414473return this ;
415474}
416475}
417- // log.debug("before super reduce. action: ", action);
476+
418477let newInstance = super . reduce ( action ) ;
419478if ( action . type === CompActionTypes . UPDATE_NODES_V2 ) {
420479// Need eval to get the value in StringControl
421480newInstance = newInstance . syncContainers ( ) ;
422481}
423- // log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424482return newInstance ;
425483}
426484
@@ -464,12 +522,9 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464522override autoHeight ( ) :boolean {
465523return this . children . autoHeight . getView ( ) ;
466524}
467-
468-
469525}
470526
471527export const TabbedContainerComp = withExposingConfigs ( TabbedContainerImplComp , [
472528new NameConfig ( "selectedTabKey" , trans ( "tabbedContainer.selectedTabKeyDesc" ) ) ,
473529NameConfigHidden ,
474530] ) ;
475-