Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import React, { useState } from "react";

import { TypeField } from "@wso2/ballerina-core";
import { Button } from "@wso2/ui-toolkit";
import { Button, Codicon } from "@wso2/ui-toolkit";


import { useHelperPaneStyles } from "./styles";
Expand Down Expand Up @@ -64,31 +64,48 @@ export function ParameterBranch(props: ParameterBranchProps) {
}
});

function toggleOptionalParams(e: any) {
function toggleOptionalParams(e?: React.MouseEvent<HTMLDivElement>) {
if (e) {
e.stopPropagation();
}
setShowOptionalParams(!showOptionalParams);
}

const shouldShowOptionalParamsDirectly = (optionalParams.length > 0 && depth === 1) ||
(requiredParams.length === 0 && optionalParams.length > 0 && depth < 3);

return (
<div data-testid="parameter-branch">
{requiredParams}
{(optionalParams.length > 0 && depth === 1) ? (
{shouldShowOptionalParamsDirectly ? (
optionalParams
) : (
<>
{optionalParams.length > 0 && (
<div className={helperStyleClass.listOptionalWrapper}>
{/* <div className={helperStyleClass.listOptionalHeader}>Optional fields </div> */}
<Button
data-testid="optional-toggle-button"
className={helperStyleClass.listOptionalBtn}
onClick={toggleOptionalParams}
appearance="secondary"
>
{showOptionalParams ? "Hide" : "Show"}
</Button>
<div className={helperStyleClass.listOptionalHeader} onClick={toggleOptionalParams}>
<Button
data-testid="optional-toggle-button"
appearance="icon"
sx={{
transition: "all 0.2s ease",
"&:hover": {
backgroundColor: "transparent !important",
},
}}
>
<Codicon
name={showOptionalParams ? "chevron-down" : "chevron-right"}
iconSx={{ fontSize: '13px' }}
/>
</Button>
<div className={helperStyleClass.listOptionalTitle}>Optional fields</div>
</div>
</div>
)}
{showOptionalParams && optionalParams.length > 0 && optionalParams}
<div style={{ marginLeft: '15px' }}>
{showOptionalParams && optionalParams.length > 0 && optionalParams}
</div>
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";

import { TypeProps } from "../../ParameterBranch";
import { useHelperPaneStyles } from "../../styles";
import { isRequiredParam } from "../../utils";
import { isRequiredParam, resetFieldValues } from "../../utils";

export default function CustomType(props: TypeProps) {
const { param, onChange } = props;
Expand All @@ -38,6 +38,12 @@ export default function CustomType(props: TypeProps) {
if (!requiredParam) {
const newSelectedState = !paramSelected;
param.selected = newSelectedState;

// When unchecking, reset the field values
if (!newSelectedState) {
resetFieldValues(param);
}

setParamSelected(newSelectedState);
onChange();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
import { TypeProps } from "../../ParameterBranch";
import { useHelperPaneStyles } from "../../styles";
import { ParameterBranch } from "../../ParameterBranch";
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection } from "../../utils";
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";

export default function InclusionType(props: TypeProps) {
const { param, depth, onChange } = props;
Expand All @@ -43,6 +43,11 @@ export default function InclusionType(props: TypeProps) {
param.selected = newSelectedState;
param.inclusionType.selected = newSelectedState;

// When unchecking, reset the field values
if (!newSelectedState) {
resetFieldValues(param);
}

// If the inclusion type has fields, update their selection state
if (param.inclusionType?.fields && param.inclusionType.fields.length > 0) {
updateFieldsSelection(param.inclusionType.fields, newSelectedState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
import { TypeProps } from "../../ParameterBranch";
import { useHelperPaneStyles } from "../../styles";
import { MemoizedParameterBranch } from "../../ParameterBranch";
import { isRequiredParam, updateFieldsSelection } from "../../utils";
import { isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";

export default function RecordType(props: TypeProps) {
const { param, depth, onChange } = props;
Expand All @@ -40,6 +40,11 @@ export default function RecordType(props: TypeProps) {
const newSelectedState = !paramSelected;
param.selected = newSelectedState;

// When unchecking, reset the field values
if (!newSelectedState) {
resetFieldValues(param);
}

// If the record has fields, update their selection state
if (param.fields && param.fields.length > 0) {
updateFieldsSelection(param.fields, newSelectedState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Codicon, Dropdown, Tooltip, Typography } from "@wso2/ui-toolkit";
import { TypeProps } from "../../ParameterBranch";
import { useHelperPaneStyles } from "../../styles";
import { ParameterBranch } from "../../ParameterBranch";
import { getSelectedUnionMember, isRequiredParam, updateFieldsSelection } from "../../utils";
import { getSelectedUnionMember, isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";

export default function UnionType(props: TypeProps) {
const { param, depth, onChange } = props;
Expand Down Expand Up @@ -184,7 +184,8 @@ export default function UnionType(props: TypeProps) {
}
}
} else {
// When unchecking, clear all member selections
// When unchecking, reset values and clear all member selections
resetFieldValues(param);
param.members.forEach((field) => {
field.selected = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* under the License.
*/
import { css } from "@emotion/css";
import { ThemeColors } from "@wso2/ui-toolkit";


const removePadding = {
Expand Down Expand Up @@ -296,22 +297,44 @@ export const useHelperPaneStyles = () => ({
listOptionalWrapper: css({
display: 'flex',
alignItems: 'center',
height: '32px',
marginBottom: '12px'
marginTop: '8px',
marginBottom: '8px'
}),
listOptionalHeader: css({
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
width: 'auto',
gap: '6px',
borderRadius: '5px',
cursor: 'pointer',
transition: 'all 0.2s ease',
border: '1px solid transparent',
'&:hover': {
backgroundColor: 'var(--vscode-list-hoverBackground)',
},
'&:hover > div:last-of-type': {
opacity: 1,
color: ThemeColors.PRIMARY,
}
}),
listOptionalTitle: css({
fontSize: '13px',
opacity: 0.7,
color: ThemeColors.ON_SURFACE_VARIANT,
transition: 'all 0.2s ease',
padding: '2px 4px',
borderRadius: '3px',
'&:hover': {
backgroundColor: 'var(--vscode-list-hoverBackground)',
}
}),
listOptionalBtn: css({
textTransform: 'none',
minWidth: '32px',
marginLeft: '8px'
}),
listOptionalHeader: css({
fontSize: '13px',
color: "gray",
fontWeight: 500,
letterSpacing: '0',
lineHeight: '14px',
paddingLeft: '0px',
}),
});


Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,41 @@ export function checkFormFieldValue(field: FormField): boolean {
return field.value !== undefined && field.value !== null;
}

export function resetFieldValues(field: FormField): void {
if (!field) return;

// Reset the field value
if (field.value !== undefined) {
field.value = undefined;
}

// Reset nested fields
if (field.fields && field.fields.length > 0) {
field.fields.forEach(nestedField => {
resetFieldValues(nestedField);
});
}

// Reset union members
if (field.members && field.members.length > 0) {
field.members.forEach(member => {
resetFieldValues(member);
});
}

// Reset inclusion type fields
if (field.inclusionType?.fields && field.inclusionType.fields.length > 0) {
field.inclusionType.fields.forEach(inclusionField => {
resetFieldValues(inclusionField);
});
}
}

export function updateFieldsSelection(fields: FormField[], selected: boolean): void {
if (!fields || !fields.length) return;

fields.forEach(field => {
// When selecting: only select required fields
// When deselecting: deselect all fields (both required and optional)
if (!selected || isRequiredParam(field)) {
field.selected = selected;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ChipExpressionEditorComponent, Context as FormContext, HelperpaneOnChan
import { useForm } from "react-hook-form";
import { debounce } from "lodash";
import ReactMarkdown from "react-markdown";
import { updateFieldsSelection } from "../Components/RecordConstructView/utils";

type ConfigureRecordPageProps = {
fileName: string;
Expand Down Expand Up @@ -142,6 +143,7 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
const { rpcClient } = useRpcContext();

const [recordModel, setRecordModel] = useState<TypeField[]>([]);
const recordModelRef = useRef<TypeField[]>([]);
const [selectedMemberName, setSelectedMemberName] = useState<string>("");
const firstRender = useRef<boolean>(true);
const sourceCode = useRef<string>(currentValue);
Expand Down Expand Up @@ -260,6 +262,7 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
}

setRecordModel([recordConfig]);
recordModelRef.current = [recordConfig];
setSelectedMemberName(newRecordModel.name);
}

Expand All @@ -270,6 +273,23 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
await fetchRecordModelFromSource(currentValue);
};


// Helper function to auto-select the first record in the model.
// Also selects required fields recursively within that record.
const autoSelectFirstRecord = (model: TypeField[]) => {
if (!model || model.length === 0) return;

const recordConfig = model[0];

// Select the first record itself
recordConfig.selected = true;

// If the record has fields, recursively select required fields
if (recordConfig.fields && recordConfig.fields.length > 0) {
updateFieldsSelection(recordConfig.fields as any, true);
}
};

const getNewRecordModel = async () => {
setIsLoading(true);
const defaultSelection = recordTypeField.recordTypeMembers[0];
Expand Down Expand Up @@ -312,7 +332,15 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
...typeFieldResponse.recordConfig
}

setRecordModel([recordConfig]);
const newModel = [recordConfig];
setRecordModel(newModel);
recordModelRef.current = newModel;

// Auto-select the first field for new models
autoSelectFirstRecord(newModel);

// Generate source with the auto-selected field
await handleModelChange(newModel);
}
setIsLoading(false);
}
Expand Down Expand Up @@ -354,7 +382,15 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
...typeFieldResponse.recordConfig
}

setRecordModel([recordConfig]);
const newModel = [recordConfig];
setRecordModel(newModel);
recordModelRef.current = newModel;

// Auto-select the first field when union member changes
autoSelectFirstRecord(newModel);

// Generate source with the auto-selected field
await handleModelChange(newModel);
}
}

Expand Down Expand Up @@ -523,6 +559,33 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
// Update local expression value when user edits in the ExpressionEditor
setLocalExpressionValue(updatedValue);

// If the expression is empty, deselect all checkboxes
if (updatedValue.trim() === '') {
// Use ref to get the latest recordModel value
const currentRecordModel = recordModelRef.current;
// Deselect all fields in the record model
if (currentRecordModel.length > 0 && currentRecordModel[0]) {
const recordConfig = currentRecordModel[0];
// Deselect the record itself
recordConfig.selected = false;
// Deselect all fields recursively
if (recordConfig.fields && recordConfig.fields.length > 0) {
updateFieldsSelection(recordConfig.fields as any, false);
}
// Update source code to empty to prevent sync issues
sourceCode.current = '';
// Update latest expression ref to prevent sync
latestExpressionToSyncRef.current = '';
// Trigger re-render by updating recordModel state with a new array reference
const updatedModel = [...currentRecordModel];
setRecordModel(updatedModel);
recordModelRef.current = updatedModel;
}
// Clear diagnostics when expression is empty
setFormDiagnostics([]);
return;
}

// Fetch diagnostics (debounced) - this will update formDiagnostics state
// The sync will be triggered by the useEffect that watches localExpressionValue
fetchDiagnostics(updatedValue);
Expand Down Expand Up @@ -585,6 +648,8 @@ export function ConfigureRecordPage(props: ConfigureRecordPageProps) {
label: member.type,
value: member.type
}))}
sx={{ width: '100%' }}
containerSx={{ width: '100%' }}
onValueChange={(value) => handleMemberChange(value)}
/>
</LabelContainer>
Expand Down
Loading