Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit84ada72

Browse files
committed
#1949 add different tab modes in PropertyView
1 parent262a60b commit84ada72

File tree

1 file changed

+92
-75
lines changed

1 file changed

+92
-75
lines changed

‎client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx‎

Lines changed: 92 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { NameGenerator } from "comps/utils";
1515
import{ScrollBar,Section,sectionNames}from"lowcoder-design";
1616
import{HintPlaceHolder}from"lowcoder-design";
1717
import_from"lodash";
18-
importReact,{useCallback,useContext,useEffect}from"react";
18+
importReact,{useContext,useEffect,useState}from"react";
1919
importstyled,{css}from"styled-components";
2020
import{IContainer}from"../containerBase/iContainer";
2121
import{SimpleContainerComp}from"../containerBase/simpleContainerComp";
@@ -34,7 +34,7 @@ import { EditorContext } from "comps/editorState";
3434
import{checkIsMobile}from"util/commonUtils";
3535
import{messageInstance}from"lowcoder-design/src/components/GlobalInstances";
3636
import{BoolControl}from"comps/controls/boolControl";
37-
import{PositionControl}from"comps/controls/dropdownControl";
37+
import{PositionControl,dropdownControl}from"comps/controls/dropdownControl";
3838
import{SliderControl}from"@lowcoder-ee/comps/controls/sliderControl";
3939
import{getBackgroundStyle}from"@lowcoder-ee/util/styleUtils";
4040

@@ -46,6 +46,15 @@ const EVENT_OPTIONS = [
4646
},
4747
]asconst;
4848

49+
constTAB_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"},
54+
]asconst;
55+
56+
constTabBehaviorControl=dropdownControl(TAB_BEHAVIOR_OPTIONS,"lazy");
57+
4958
constchildrenMap={
5059
tabs:TabsOptionControl,
5160
selectedTabKey:stringExposingStateControl("key","Tab1"),
@@ -61,7 +70,7 @@ const childrenMap = {
6170
onEvent:eventHandlerControl(EVENT_OPTIONS),
6271
disabled:BoolCodeControl,
6372
showHeader:withDefault(BoolControl,true),
64-
destroyInactiveTab:withDefault(BoolControl,false),
73+
tabBehavior:withDefault(TabBehaviorControl,"lazy"),
6574
style:styleControl(TabContainerStyle,'style'),
6675
headerStyle:styleControl(ContainerHeaderStyle,'headerStyle'),
6776
bodyStyle:styleControl(TabBodyStyle,'bodyStyle'),
@@ -72,7 +81,7 @@ const childrenMap = {
7281

7382
typeViewProps=RecordConstructorToView<typeofchildrenMap>;
7483
typeTabbedContainerProps=ViewProps&{dispatch:DispatchType};
75-
84+
7685
constgetStyle=(
7786
style:TabContainerStyleType,
7887
headerStyle:ContainerHeaderStyleType,
@@ -138,11 +147,11 @@ const getStyle = (
138147
`;
139148
};
140149

141-
constStyledTabs=styled(Tabs)<{
150+
constStyledTabs=styled(Tabs)<{
142151
$style:TabContainerStyleType;
143152
$headerStyle:ContainerHeaderStyleType;
144153
$bodyStyle:TabBodyStyleType;
145-
$isMobile?:boolean;
154+
$isMobile?:boolean;
146155
$showHeader?:boolean;
147156
$animationStyle:AnimationStyleType
148157
}>`
@@ -157,13 +166,12 @@ const StyledTabs = styled(Tabs)<{
157166
158167
.ant-tabs-content {
159168
height: 100%;
160-
// margin-top: -16px;
169+
161170
}
162171
163172
.ant-tabs-nav {
164173
display:${(props)=>(props.$showHeader ?"block" :"none")};
165174
padding: 0${(props)=>(props.$isMobile ?16 :24)}px;
166-
// background: white;
167175
margin: 0px;
168176
}
169177
@@ -197,27 +205,20 @@ const TabbedContainer = (props: TabbedContainerProps) => {
197205
headerStyle,
198206
bodyStyle,
199207
horizontalGridCells,
200-
destroyInactiveTab,
208+
tabBehavior,
201209
}=props;
202210

203211
constvisibleTabs=tabs.filter((tab)=>!tab.hidden);
204212
constselectedTab=visibleTabs.find((tab)=>tab.key===props.selectedTabKey.value);
205-
constactiveKey=selectedTab
206-
?selectedTab.key
207-
:visibleTabs.length>0
208-
?visibleTabs[0].key
209-
:undefined;
210-
211-
constonTabClick=useCallback(
212-
(key:string,event:React.KeyboardEvent<Element>|React.MouseEvent<Element,MouseEvent>)=>{
213-
// log.debug("onTabClick. event: ", event);
214-
consttarget=event.target;
215-
(targetasany).parentNode.click
216-
?(targetasany).parentNode.click()
217-
:(targetasany).parentNode.parentNode.click();
218-
},
219-
[]
220-
);
213+
constactiveKey=selectedTab?selectedTab.key:visibleTabs.length>0 ?visibleTabs[0].key :undefined;
214+
215+
// Placeholder-based lazy loading — only for "lazy" mode
216+
const[loadedTabs,setLoadedTabs]=useState<Set<string>>(newSet());
217+
useEffect(()=>{
218+
if(tabBehavior==="lazy"&&activeKey){
219+
setLoadedTabs((prev:Set<string>)=>newSet([...prev,activeKey]));
220+
}
221+
},[tabBehavior,activeKey]);
221222

222223
consteditorState=useContext(EditorContext);
223224
constmaxWidth=editorState.getAppSettings().maxWidth;
@@ -230,23 +231,38 @@ const TabbedContainer = (props: TabbedContainerProps) => {
230231
constchildDispatch=wrapDispatch(wrapDispatch(dispatch,"containers"),id);
231232
constcontainerProps=containers[id].children;
232233
consthasIcon=tab.icon.props.value;
234+
233235
constlabel=(
234236
<>
235-
{tab.iconPosition==="left"&&hasIcon&&(
236-
<spanstyle={{marginRight:"4px"}}>{tab.icon}</span>
237-
)}
237+
{tab.iconPosition==="left"&&hasIcon&&<spanstyle={{marginRight:4}}>{tab.icon}</span>}
238238
{tab.label}
239-
{tab.iconPosition==="right"&&hasIcon&&(
240-
<spanstyle={{marginLeft:"4px"}}>{tab.icon}</span>
241-
)}
239+
{tab.iconPosition==="right"&&hasIcon&&<spanstyle={{marginLeft:4}}>{tab.icon}</span>}
242240
</>
243241
);
244-
return{
245-
label,
246-
key:tab.key,
247-
forceRender:!destroyInactiveTab,
248-
destroyInactiveTab:destroyInactiveTab,
249-
children:(
242+
243+
// Item-level forceRender mapping
244+
constforceRender:boolean=tabBehavior==="keep-alive";
245+
246+
// Render content (placeholder only for "lazy" & not yet opened)
247+
constrenderTabContent=()=>{
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(
250266
<BackgroundColorContext.Providervalue={bodyStyle.background}>
251267
<ScrollBarstyle={{height:props.autoHeight ?"auto" :"100%",margin:"0px",padding:"0px"}}hideScrollbar={!props.showVerticalScrollbar}overflow={props.autoHeight ?'hidden':'scroll'}>
252268
<ContainerInTab
@@ -260,41 +276,49 @@ const TabbedContainer = (props: TabbedContainerProps) => {
260276
/>
261277
</ScrollBar>
262278
</BackgroundColorContext.Provider>
263-
)
264-
}
265-
})
279+
);
280+
};
281+
282+
return{
283+
label,
284+
key:tab.key,
285+
forceRender,// true only for keep-alive
286+
children:renderTabContent(),
287+
};
288+
});
266289

267290
return(
268291
<divstyle={{padding:props.style.margin,height:props.autoHeight ?"auto" :"100%"}}>
269-
<BackgroundColorContext.Providervalue={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>
292+
<BackgroundColorContext.Providervalue={headerStyle.headerBackground}>
293+
<StyledTabs
294+
destroyOnHidden={tabBehavior==="destroy"}
295+
$animationStyle={props.animationStyle}
296+
tabPosition={props.placement}
297+
activeKey={activeKey}
298+
$style={style}
299+
$headerStyle={headerStyle}
300+
$bodyStyle={bodyStyle}
301+
$showHeader={showHeader}
302+
onChange={(key)=>{
303+
if(key!==props.selectedTabKey.value){
304+
props.selectedTabKey.onChange(key);
305+
props.onEvent("change");
306+
if(tabBehavior==="lazy"){
307+
setLoadedTabs((prev:Set<string>)=>newSet([...prev,key]));
308+
}
309+
}
310+
}}
311+
animated
312+
$isMobile={isMobile}
313+
items={tabItems}
314+
tabBarGutter={props.tabsGutter}
315+
centered={props.tabsCentered}
316+
/>
317+
</BackgroundColorContext.Provider>
318+
</div>
294319
);
295320
};
296321

297-
298322
exportconstTabbedContainerBaseComp=(function(){
299323
returnnewUICompBuilder(childrenMap,(props,dispatch)=>{
300324
return(
@@ -313,14 +337,14 @@ export const TabbedContainerBaseComp = (function () {
313337
})}
314338
{children.selectedTabKey.propertyView({label:trans("prop.defaultValue")})}
315339
</Section>
316-
340+
317341
{["logic","both"].includes(useContext(EditorContext).editorModeStatus)&&(
318342
<Sectionname={sectionNames.interaction}>
319343
{children.onEvent.getPropertyView()}
320344
{disabledPropertyView(children)}
321345
{hiddenPropertyView(children)}
322346
{children.showHeader.propertyView({label:trans("tabbedContainer.showTabs")})}
323-
{children.destroyInactiveTab.propertyView({label:trans("tabbedContainer.destroyInactiveTab")})}
347+
{children.tabBehavior.propertyView({label:"Tab Behavior"})}
324348
</Section>
325349
)}
326350

@@ -371,21 +395,18 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
371395
constactions:CompAction[]=[];
372396
Object.keys(containers).forEach((id)=>{
373397
if(!ids.has(id)){
374-
// log.debug("syncContainers delete. ids=", ids, " id=", id);
375398
actions.push(wrapChildAction("containers",wrapChildAction(id,deleteCompAction())));
376399
}
377400
});
378401
// new
379402
ids.forEach((id)=>{
380403
if(!containers.hasOwnProperty(id)){
381-
// log.debug("syncContainers new containers: ", containers, " id: ", id);
382404
actions.push(
383405
wrapChildAction("containers",addMapChildAction(id,{layout:{},items:{}}))
384406
);
385407
}
386408
});
387409

388-
// log.debug("syncContainers. actions: ", actions);
389410
letinstance=this;
390411
actions.forEach((action)=>{
391412
instance=instance.reduce(action);
@@ -414,13 +435,11 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
414435
returnthis;
415436
}
416437
}
417-
// log.debug("before super reduce. action: ", action);
418438
letnewInstance=super.reduce(action);
419439
if(action.type===CompActionTypes.UPDATE_NODES_V2){
420440
// Need eval to get the value in StringControl
421441
newInstance=newInstance.syncContainers();
422442
}
423-
// log.debug("reduce. instance: ", this, " newInstance: ", newInstance);
424443
returnnewInstance;
425444
}
426445

@@ -464,8 +483,6 @@ class TabbedContainerImplComp extends TabbedContainerBaseComp implements IContai
464483
overrideautoHeight():boolean{
465484
returnthis.children.autoHeight.getView();
466485
}
467-
468-
469486
}
470487

471488
exportconstTabbedContainerComp=withExposingConfigs(TabbedContainerImplComp,[

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp