From 087d0a2d0083418d02f60c9ef4e0ddca4d85a0e8 Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Wed, 26 Nov 2025 15:12:29 +0530 Subject: [PATCH 1/8] extend the implementation of the chipExpressionEditor to have a config based modifications --- .../ExpandedEditor/modes/TemplateMode.tsx | 30 ++++ .../components/editors/ExpressionEditor.tsx | 24 +--- .../components/editors/ExpressionField.tsx | 132 ++++++++++++++++-- .../ChipExpressionEditor/CodeUtils.ts | 21 +-- .../components/ChipExpressionEditor.tsx | 49 +++++-- .../configurations/IConfiguration.tsx | 35 +++++ .../ChipExpressionEditor/utils.ts | 10 +- .../TextExpressionEditor/TextModeEditor.tsx | 64 +++------ .../src/views/BI/HelperPaneNew/utils/utils.ts | 4 +- 9 files changed, 257 insertions(+), 112 deletions(-) create mode 100644 workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration.tsx diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx index 2213b4f3bb..59fcd6eb7f 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx @@ -26,6 +26,7 @@ import { MarkdownPreview } from "../controls/MarkdownPreview"; import { transformExpressionToMarkdown } from "../utils/transformToMarkdown"; import { useFormContext } from "../../../../context/form"; import { ErrorBanner } from "@wso2/ui-toolkit"; +import { ChipExpressionEditorDefaultConfiguration } from "../../MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration"; const ExpressionContainer = styled.div` width: 100%; @@ -36,6 +37,34 @@ const ExpressionContainer = styled.div` overflow: hidden; `; +export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { + getHelperValue(value: string): string { + return `\$\{${value}\}`; + } + getSerializationPrefix() { + return "`"; + } + getSerializationSuffix() { + return "`"; + } + serializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value.trim().slice(prefix.length, value.trim().length - suffix.length); + } + return value; + } + deserializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value; + } + return `${prefix}${value}${suffix}`; + } +} + export const TemplateMode: React.FC = ({ value, onChange, @@ -150,6 +179,7 @@ export const TemplateMode: React.FC = ({ onEditorViewReady={setEditorView} toolbarRef={toolbarRef} enableListContinuation={true} + configuration={new RawTemplateEditorConfig()} /> )} diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx index 866a72b2e7..66e95aad48 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx @@ -581,14 +581,6 @@ export const ExpressionEditor = (props: ExpressionEditorProps) => { return; } - // Auto-add quotes when switching from TEXT to EXP if not present - if (inputMode === InputMode.TEXT && value === InputMode.EXP) { - if (currentValue && typeof currentValue === 'string' && - !currentValue.startsWith('"') && !currentValue.endsWith('"')) { - setValue(key, `"${currentValue}"`); - } - } - setInputMode(value); }; @@ -698,14 +690,13 @@ export const ExpressionEditor = (props: ExpressionEditorProps) => {
{ @@ -714,13 +705,12 @@ export const ExpressionEditor = (props: ExpressionEditorProps) => { setFormDiagnostics([]); // Use ref to get current mode (not stale closure value) const currentMode = inputModeRef.current; - const rawValue = currentMode === InputMode.TEMPLATE && rawExpression ? rawExpression(updatedValue) : updatedValue; - onChange(rawValue); + onChange(updatedValue); if (getExpressionEditorDiagnostics && (currentMode === InputMode.EXP || currentMode === InputMode.TEMPLATE)) { getExpressionEditorDiagnostics( - (required ?? !field.optional) || rawValue !== '', - rawValue, + (required ?? !field.optional) || updatedValue !== '', + updatedValue, key, getPropertyFromFormField(field) ); @@ -729,18 +719,18 @@ export const ExpressionEditor = (props: ExpressionEditorProps) => { // Check if the current character is a trigger character const triggerCharacter = updatedCursorPosition > 0 - ? triggerCharacters.find((char) => rawValue[updatedCursorPosition - 1] === char) + ? triggerCharacters.find((char) => updatedValue[updatedCursorPosition - 1] === char) : undefined; if (triggerCharacter) { await retrieveCompletions( - rawValue, + updatedValue, getPropertyFromFormField(field), updatedCursorPosition, triggerCharacter ); } else { await retrieveCompletions( - rawValue, + updatedValue, getPropertyFromFormField(field), updatedCursorPosition ); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index 53d79a469e..33ba4f6b29 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -31,9 +31,11 @@ import { InputMode } from './MultiModeExpressionEditor/ChipExpressionEditor/type import { LineRange } from '@wso2/ballerina-core/lib/interfaces/common'; import { HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; +import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration'; export interface ExpressionField { inputMode: InputMode; + primaryMode: InputMode; name: string; value: string; fileName?: string; @@ -94,8 +96,65 @@ const EditorRibbon = ({ onClick }: { onClick: () => void }) => { ); }; +export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { + getHelperValue(value: string): string { + return `\$\{${value}\}`; + } + getSerializationPrefix() { + return "string `"; + } + getSerializationSuffix() { + return "`"; + } + serializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value.trim().slice(prefix.length, value.trim().length - suffix.length); + } + return value; + } + deserializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value; + } + return `${prefix}${value}${suffix}`; + } +} + +export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { + getHelperValue(value: string): string { + return `\$\{${value}\}`; + } + getSerializationPrefix() { + return "`"; + } + getSerializationSuffix() { + return "`"; + } + serializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value.trim().slice(prefix.length, value.trim().length - suffix.length); + } + return value; + } + deserializeValue(value: string): string { + const suffix = this.getSerializationSuffix(); + const prefix = this.getSerializationPrefix(); + if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { + return value; + } + return `${prefix}${value}${suffix}`; + } +} + export const ExpressionField: React.FC = ({ inputMode, + primaryMode, name, value, completions, @@ -127,25 +186,73 @@ export const ExpressionField: React.FC = ({ onOpenExpandedMode, isInExpandedMode }) => { - if (inputMode === InputMode.TEXT || inputMode === InputMode.RECORD) { + class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { + getHelperValue(value: string): string { + if (primaryMode === InputMode.TEXT || primaryMode === InputMode.TEMPLATE) { + return `\$\{${value}\}`; + } + return value; + } + } + if (inputMode === InputMode.TEXT) { return ( + + ); + } + if (inputMode === InputMode.TEMPLATE) { + return ( + + + ); + } + if (inputMode === InputMode.RECORD) { + return ( + ); @@ -166,6 +273,7 @@ export const ExpressionField: React.FC = ({ onOpenExpandedMode={onOpenExpandedMode} onRemove={onRemove} isInExpandedMode={isInExpandedMode} + configuration={new ChipExpressionEditorConfig()} /> ); }; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts index 9c6a1a2ad6..62c358093c 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts @@ -37,8 +37,6 @@ export type TokenStream = number[]; export type TokensChangePayload = { tokens: TokenStream; - rawValue?: string; // Raw expression (e.g., `${var}`) - sanitizedValue?: string; // Sanitized expression (e.g., ${var}) }; export type CursorInfo = { @@ -225,23 +223,12 @@ export const tokenField = StateField.define({ for (let effect of tr.effects) { if (effect.is(tokensChangeEffect)) { const payload = effect.value; - const sanitizedDoc = tr.newDoc.toString(); - - // Parse tokens using the raw value if provided, otherwise use sanitized - const valueForParsing = payload.rawValue || sanitizedDoc; - tokens = getParsedExpressionTokens(payload.tokens, valueForParsing); - - // If we have both raw and sanitized values, map positions - if (payload.rawValue && payload.sanitizedValue) { - tokens = tokens.map(token => ({ - ...token, - start: mapRawToSanitized(token.start, payload.rawValue!, payload.sanitizedValue!), - end: mapRawToSanitized(token.end, payload.rawValue!, payload.sanitizedValue!) - })); - } + const currentValue = tr.newDoc.toString(); + + tokens = getParsedExpressionTokens(payload.tokens, currentValue); // Detect compounds once when tokens change - compounds = detectTokenPatterns(tokens, sanitizedDoc); + compounds = detectTokenPatterns(tokens, currentValue); return { tokens, compounds }; } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx index a8ea859d6e..fc6a394962 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx @@ -37,7 +37,7 @@ import { buildOnSelectionChange, SyncDocValueWithPropValue } from "../CodeUtils"; -import { mapSanitizedToRaw } from "../utils"; +import { mapSanitizedToRaw, TOKEN_START_CHAR_OFFSET_INDEX } from "../utils"; import { history } from "@codemirror/commands"; import { autocompletion } from "@codemirror/autocomplete"; import { FloatingButtonContainer, FloatingToggleButton, ChipEditorContainer } from "../styles"; @@ -49,6 +49,14 @@ import FXButton from "./FxButton"; import { HelperPaneToggleButton } from "./HelperPaneToggleButton"; import { HelperPane } from "./HelperPane"; import { listContinuationKeymap } from "../../../ExpandedEditor/utils/templateUtils"; +import { ChipExpressionEditorDefaultConfiguration } from "../configurations/IConfiguration"; + +export class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { + getHelperValue(value: string): string { + return value; + } +} + type HelperPaneState = { isOpen: boolean; @@ -90,11 +98,12 @@ export type ChipExpressionEditorComponentProps = { onEditorViewReady?: (view: EditorView) => void; toolbarRef?: React.RefObject; enableListContinuation?: boolean; + configuration?: ChipExpressionEditorDefaultConfiguration; } export const ChipExpressionEditorComponent = (props: ChipExpressionEditorComponentProps) => { + const { configuration = new ChipExpressionEditorConfig() } = props; const [helperPaneState, setHelperPaneState] = useState({ isOpen: false, top: 0, left: 0 }); - const editorRef = useRef(null); const helperPaneRef = useRef(null); const fieldContainerRef = useRef(null); @@ -114,7 +123,7 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone const handleChangeListner = buildOnChangeListner((newValue, cursor) => { completionsFetchScheduledRef.current = true; - props.onChange(newValue, cursor.position.to); + props.onChange(configuration.deserializeValue(newValue), cursor.position.to); const textBeforeCursor = newValue.slice(0, cursor.position.to); const lastNonSpaceChar = textBeforeCursor.trimEnd().slice(-1); const isTrigger = lastNonSpaceChar === '+' || lastNonSpaceChar === ':'; @@ -164,7 +173,7 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone }); const onHelperItemSelect = async (value: string, options: HelperpaneOnChangeOptions) => { - const newValue = value + const newValue = configuration.getHelperValue(value); if (!viewRef.current) return; const view = viewRef.current; @@ -187,7 +196,7 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone let functionDef = newValue; let prefix = ''; let suffix = ''; - + // Check if it's within a string template const stringTemplateMatch = newValue.match(/^(.*\$\{)([^}]+)(\}.*)$/); if (stringTemplateMatch) { @@ -195,12 +204,12 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone functionDef = stringTemplateMatch[2]; suffix = stringTemplateMatch[3]; } - + let cursorPositionForExtraction = from + prefix.length + functionDef.length - 1; if (functionDef.endsWith(')}')) { cursorPositionForExtraction -= 1; } - + const fnSignature = await props.extractArgsFromFunction(functionDef, cursorPositionForExtraction); if (fnSignature && fnSignature.args && fnSignature.args.length > 0) { @@ -339,8 +348,14 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone useEffect(() => { if (props.value == null || !viewRef.current) return; + const serializedValue = configuration.serializeValue(props.value.trim()); + const deserializeValue = configuration.deserializeValue(props.value.trim()); + if (deserializeValue.trim() !== props.value.trim()) { + props.onChange(deserializeValue.trim(), deserializeValue.trim().length); + return + } const updateEditorState = async () => { - const sanitizedValue = props.sanitizedExpression ? props.sanitizedExpression(props.value) : props.value; + const sanitizedValue = props.sanitizedExpression ? props.sanitizedExpression(serializedValue) : serializedValue; const currentDoc = viewRef.current!.state.doc.toString(); const isExternalUpdate = sanitizedValue !== currentDoc; @@ -348,15 +363,17 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone const startLine = props.targetLineRange?.startLine; const tokenStream = await expressionEditorRpcManager?.getExpressionTokens( - props.value, + deserializeValue, props.fileName, startLine !== undefined ? startLine : undefined ); + const prefixCorrectedTokenStream = tokenStream; + if (tokenStream && tokenStream.length >= 5) { + prefixCorrectedTokenStream[TOKEN_START_CHAR_OFFSET_INDEX] -= configuration.getSerializationPrefix().length; + } setIsTokenUpdateScheduled(false); - const effects = tokenStream ? [tokensChangeEffect.of({ - tokens: tokenStream, - rawValue: props.value, - sanitizedValue: sanitizedValue + const effects = prefixCorrectedTokenStream ? [tokensChangeEffect.of({ + tokens: prefixCorrectedTokenStream })] : []; const changes = isExternalUpdate ? { from: 0, to: viewRef.current!.state.doc.length, insert: sanitizedValue } @@ -438,7 +455,11 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone ...(props.isInExpandedMode ? { height: '100%' } : { height: 'auto' }) }}> {!props.isInExpandedMode && } -
+
; -export const TextModeEditor: React.FC = ({ - name, - value, - autoFocus, - ariaLabel, - placeholder, - onChange, - onFocus, - onBlur, - onSave, - onCancel, - onRemove, - growRange, - exprRef, - anchorRef, - onOpenExpandedMode, - isInExpandedMode, -}) => { - - const handleOnChange = async (value: string, updatedCursorPosition: number) => { - const newValue = "\"" + value + "\""; - onChange(newValue, updatedCursorPosition); - } +export const TextModeEditor: React.FC = (props) => { return ( - } - ariaLabel={ariaLabel} - onChange={handleOnChange} - onFocus={onFocus} - onBlur={onBlur} - onSave={onSave} - onCancel={onCancel} - onRemove={onRemove} - enableExIcon={false} - growRange={growRange} - sx={{ paddingInline: '0' }} - placeholder={placeholder} + - {onOpenExpandedMode && !isInExpandedMode && ( + {props.onOpenExpandedMode && !props.isInExpandedMode && (
- +
diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/utils/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/utils/utils.ts index e65fd742a3..0cbe367348 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/utils/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/utils/utils.ts @@ -19,6 +19,6 @@ import { InputMode } from "@wso2/ballerina-side-panel"; // Wraps a value in template interpolation syntax ${} if in template mode -export const wrapInTemplateInterpolation = (value: string, inputMode?: InputMode): string => { - return inputMode === InputMode.TEMPLATE ? `\${${value}}` : value; +export const wrapInTemplateInterpolation = (value: string, _inputMode?: InputMode): string => { + return value; }; From c527bfc18c76d200d129a4c7c6f9a4652d4bca8c Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Wed, 26 Nov 2025 15:28:06 +0530 Subject: [PATCH 2/8] Fix record config model after editor changes --- .../components/editors/ExpressionField.tsx | 29 ++--- .../RecordConfigPreviewEditor.tsx | 100 ++++++++++++++++++ 2 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor.tsx diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index 33ba4f6b29..c05a383f32 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -32,6 +32,7 @@ import { LineRange } from '@wso2/ballerina-core/lib/interfaces/common'; import { HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration'; +import RecordConfigPreviewEditor from './MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor'; export interface ExpressionField { inputMode: InputMode; @@ -215,7 +216,7 @@ export const ExpressionField: React.FC = ({ ); } - if (inputMode === InputMode.TEMPLATE) { + if (inputMode === InputMode.TEMPLATE) { return ( = ({ } if (inputMode === InputMode.RECORD) { return ( - ); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor.tsx new file mode 100644 index 0000000000..a09e1b2717 --- /dev/null +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor.tsx @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FormExpressionEditor } from "@wso2/ui-toolkit"; +import { ExpressionField } from "../../ExpressionField"; +import React from "react"; +import { getValueForTextModeEditor } from "../../utils"; +import styled from "@emotion/styled"; +import { FloatingToggleButton } from "../ChipExpressionEditor/components/FloatingToggleButton"; +import { ExpandIcon } from "../ChipExpressionEditor/components/FloatingButtonIcons"; + +const EditorContainer = styled.div` + width: 100%; + position: relative; + + #text-mode-editor-expand { + opacity: 0; + transition: opacity 0.2s ease-in-out; + } + + &:hover #text-mode-editor-expand { + opacity: 1; + } +`; + +type RecordConfigPreviewEditorProps = Pick; + +export const RecordConfigPreviewEditor: React.FC = ({ + name, + value, + autoFocus, + ariaLabel, + placeholder, + onChange, + onFocus, + onBlur, + onSave, + onCancel, + onRemove, + growRange, + exprRef, + anchorRef, + onOpenExpandedMode, + isInExpandedMode, +}) => { + + const handleOnChange = async (value: string, updatedCursorPosition: number) => { + const newValue = "\"" + value + "\""; + onChange(newValue, updatedCursorPosition); + } + + return ( + + } + ariaLabel={ariaLabel} + onChange={handleOnChange} + onFocus={onFocus} + onBlur={onBlur} + onSave={onSave} + onCancel={onCancel} + onRemove={onRemove} + enableExIcon={false} + growRange={growRange} + sx={{ paddingInline: '0' }} + placeholder={placeholder} + /> + {onOpenExpandedMode && !isInExpandedMode && ( +
+ + + +
+ )} +
+ ); +}; + +export default RecordConfigPreviewEditor From 439ff7ac9094da2a54b3f3fe15c691717872853b Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Thu, 27 Nov 2025 10:03:21 +0530 Subject: [PATCH 3/8] refactor configuration --- .../ExpandedEditor/modes/TemplateMode.tsx | 30 +---- .../components/editors/ExpressionField.tsx | 104 ++++-------------- ...ion.tsx => ChipExpressionDefaultConfig.ts} | 0 .../components/ChipExpressionEditor.tsx | 10 +- .../Configurations.tsx | 81 ++++++++++++++ 5 files changed, 108 insertions(+), 117 deletions(-) rename workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/{configurations/IConfiguration.tsx => ChipExpressionDefaultConfig.ts} (100%) create mode 100644 workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx index 59fcd6eb7f..42681e0ab1 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TemplateMode.tsx @@ -26,7 +26,7 @@ import { MarkdownPreview } from "../controls/MarkdownPreview"; import { transformExpressionToMarkdown } from "../utils/transformToMarkdown"; import { useFormContext } from "../../../../context/form"; import { ErrorBanner } from "@wso2/ui-toolkit"; -import { ChipExpressionEditorDefaultConfiguration } from "../../MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration"; +import { RawTemplateEditorConfig } from "../../MultiModeExpressionEditor/Configurations"; const ExpressionContainer = styled.div` width: 100%; @@ -37,34 +37,6 @@ const ExpressionContainer = styled.div` overflow: hidden; `; -export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - return `\$\{${value}\}`; - } - getSerializationPrefix() { - return "`"; - } - getSerializationSuffix() { - return "`"; - } - serializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value.trim().slice(prefix.length, value.trim().length - suffix.length); - } - return value; - } - deserializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value; - } - return `${prefix}${value}${suffix}`; - } -} - export const TemplateMode: React.FC = ({ value, onChange, diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index c05a383f32..1ad98ca0de 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -31,8 +31,9 @@ import { InputMode } from './MultiModeExpressionEditor/ChipExpressionEditor/type import { LineRange } from '@wso2/ballerina-core/lib/interfaces/common'; import { HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; -import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/configurations/IConfiguration'; +import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig'; import RecordConfigPreviewEditor from './MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor'; +import { RawTemplateEditorConfig, StringTemplateEditorConfig } from './MultiModeExpressionEditor/Configurations'; export interface ExpressionField { inputMode: InputMode; @@ -97,62 +98,6 @@ const EditorRibbon = ({ onClick }: { onClick: () => void }) => { ); }; -export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - return `\$\{${value}\}`; - } - getSerializationPrefix() { - return "string `"; - } - getSerializationSuffix() { - return "`"; - } - serializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value.trim().slice(prefix.length, value.trim().length - suffix.length); - } - return value; - } - deserializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value; - } - return `${prefix}${value}${suffix}`; - } -} - -export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - return `\$\{${value}\}`; - } - getSerializationPrefix() { - return "`"; - } - getSerializationSuffix() { - return "`"; - } - serializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value.trim().slice(prefix.length, value.trim().length - suffix.length); - } - return value; - } - deserializeValue(value: string): string { - const suffix = this.getSerializationSuffix(); - const prefix = this.getSerializationPrefix(); - if (value.trim().startsWith(prefix) && value.trim().endsWith(suffix)) { - return value; - } - return `${prefix}${value}${suffix}`; - } -} - export const ExpressionField: React.FC = ({ inputMode, primaryMode, @@ -194,6 +139,28 @@ export const ExpressionField: React.FC = ({ } return value; } + } + if (inputMode === InputMode.RECORD) { + return ( + + ); } if (inputMode === InputMode.TEXT) { return ( @@ -237,29 +204,6 @@ export const ExpressionField: React.FC = ({ ); } - if (inputMode === InputMode.RECORD) { - return ( - - - ); - } return ( Date: Thu, 27 Nov 2025 11:34:48 +0530 Subject: [PATCH 4/8] fix prompt editor invalid $ sign diagnostics issue --- .../components/editors/ExpressionEditor.tsx | 21 +------------------ .../components/editors/ExpressionField.tsx | 8 ++++--- .../ChipExpressionDefaultConfig.ts | 4 +++- .../ChipExpressionEditor/CodeUtils.ts | 15 +++++++++++++ .../components/ChipExpressionEditor.tsx | 8 +++++-- .../Configurations.tsx | 16 +++++++++----- .../BI/HelperPaneNew/Views/DocumentConfig.tsx | 11 +++++----- 7 files changed, 46 insertions(+), 37 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx index 66e95aad48..812e02a4ee 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionEditor.tsx @@ -449,26 +449,7 @@ export const ExpressionEditor = (props: ExpressionEditorProps) => { setInputMode(InputMode.EXP); return; } - if (newInputMode === InputMode.TEXT - && typeof initialFieldValue.current === 'string' - && initialFieldValue.current.trim() !== '' - && !(initialFieldValue.current.trim().startsWith("\"") - && initialFieldValue.current.trim().endsWith("\"") - ) - ) { - setInputMode(InputMode.EXP) - } else if (newInputMode === InputMode.TEMPLATE) { - if (sanitizedExpression && rawExpression) { - const sanitized = sanitizedExpression(initialFieldValue.current as string); - if (sanitized !== initialFieldValue.current || !initialFieldValue.current || initialFieldValue.current.trim() === '') { - setInputMode(InputMode.TEMPLATE); - } else { - setInputMode(InputMode.EXP); - } - } - } else { - setInputMode(newInputMode); - } + setInputMode(newInputMode) }, [field?.valueTypeConstraint, recordTypeField]); const handleFocus = async (controllerOnChange?: (value: string) => void) => { diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index 1ad98ca0de..64ff92ba4f 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -27,13 +27,14 @@ import { } from '@wso2/ui-toolkit'; import { S } from './ExpressionEditor'; import TextModeEditor from './MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor'; -import { InputMode } from './MultiModeExpressionEditor/ChipExpressionEditor/types'; +import { InputMode, TokenType } from './MultiModeExpressionEditor/ChipExpressionEditor/types'; import { LineRange } from '@wso2/ballerina-core/lib/interfaces/common'; import { HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig'; import RecordConfigPreviewEditor from './MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor'; import { RawTemplateEditorConfig, StringTemplateEditorConfig } from './MultiModeExpressionEditor/Configurations'; +import { ParsedToken } from './MultiModeExpressionEditor/ChipExpressionEditor/utils'; export interface ExpressionField { inputMode: InputMode; @@ -133,8 +134,9 @@ export const ExpressionField: React.FC = ({ isInExpandedMode }) => { class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - if (primaryMode === InputMode.TEXT || primaryMode === InputMode.TEMPLATE) { + getHelperValue(value: string, token?: ParsedToken): string { + const isTextOrTemplateMode = primaryMode === InputMode.TEXT || primaryMode === InputMode.TEMPLATE; + if (isTextOrTemplateMode && token && token.type !== TokenType.FUNCTION ) { return `\$\{${value}\}`; } return value; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts index d7a9b7aff6..5ba0bc0e9a 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts @@ -16,8 +16,10 @@ * under the License. */ +import { ParsedToken } from "./utils"; + export abstract class ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string) { + getHelperValue(value: string, token?: ParsedToken) { return value; } getSerializationPrefix() { diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts index 62c358093c..b96313ed24 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/CodeUtils.ts @@ -605,3 +605,18 @@ export const buildHelperPaneKeymap = (getIsHelperPaneOpen: () => boolean, onClos } ]; }; + +export const isSelectionOnToken = (from: number, to: number, view: EditorView): ParsedToken => { + if (!view) return undefined; + const { tokens, compounds } = view.state.field(tokenField); + + const matchingCompound = compounds.find( + compound => compound.start === from && compound.end === to + ); + if (matchingCompound) return undefined; + + const matchingToken = tokens.find( + token => token.start === from && token.end === to + ); + return matchingToken; +}; \ No newline at end of file diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx index 3a803a02a3..cdfd896e48 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx @@ -35,7 +35,8 @@ import { CursorInfo, buildOnFocusOutListner, buildOnSelectionChange, - SyncDocValueWithPropValue + SyncDocValueWithPropValue, + isSelectionOnToken } from "../CodeUtils"; import { mapSanitizedToRaw, TOKEN_START_CHAR_OFFSET_INDEX } from "../utils"; import { history } from "@codemirror/commands"; @@ -167,13 +168,16 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone }); const onHelperItemSelect = async (value: string, options: HelperpaneOnChangeOptions) => { - const newValue = configuration.getHelperValue(value); if (!viewRef.current) return; const view = viewRef.current; // Use saved selection if available, otherwise fall back to current selection const currentSelection = savedSelectionRef.current || view.state.selection.main; const { from, to } = options?.replaceFullText ? { from: 0, to: view.state.doc.length } : currentSelection; + + // Only apply getHelperValue if selection is not on a token + const selectionIsOnToken = isSelectionOnToken(currentSelection.from, currentSelection.to, view); + const newValue = selectionIsOnToken ? value : configuration.getHelperValue(value); let finalValue = newValue; let cursorPosition = from + newValue.length; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx index c803fdc164..8f812bfdef 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx @@ -17,10 +17,14 @@ */ import { ChipExpressionEditorDefaultConfiguration } from "./ChipExpressionEditor/ChipExpressionDefaultConfig"; +import { TokenType } from "./ChipExpressionEditor/types"; +import { ParsedToken } from "./ChipExpressionEditor/utils"; export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - return `\$\{${value}\}`; + getHelperValue(value: string, token?: ParsedToken): string { + if (token?.type !== TokenType.FUNCTION) { + return `\$\{${value}\}`; + } } getSerializationPrefix() { return "string `"; @@ -47,8 +51,10 @@ export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfi } export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { - return `\$\{${value}\}`; + getHelperValue(value: string, token?: ParsedToken): string { + if (token?.type !== TokenType.FUNCTION) { + return `\$\{${value}\}`; + } } getSerializationPrefix() { return "`"; @@ -75,7 +81,7 @@ export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfigur } export class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string): string { + getHelperValue(value: string, token?: ParsedToken): string { return value; } } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/DocumentConfig.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/DocumentConfig.tsx index fbef883dad..54a40f3339 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/DocumentConfig.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Views/DocumentConfig.tsx @@ -44,9 +44,8 @@ type DocumentConfigProps = { const AI_DOCUMENT_TYPES = Object.values(AIDocumentType); // Helper function to wrap content in document structure -const wrapInDocumentType = (documentType: AIDocumentType, content: string, addInterpolation: boolean = true): string => { - const docStructure = `<${documentType}>{content: ${content}}`; - return addInterpolation ? `\${${docStructure}}` : docStructure; +const wrapInDocumentType = (documentType: AIDocumentType, content: string): string => { + return`<${documentType}>{content: ${content}}`; }; export const DocumentConfig = ({ onChange, onClose, targetLineRange, filteredCompletions, currentValue, handleRetrieveCompletions, isInModal, inputMode }: DocumentConfigProps) => { @@ -164,13 +163,13 @@ export const DocumentConfig = ({ onChange, onClose, targetLineRange, filteredCom if (isAIDocumentType) { // For AI document types, wrap in string interpolation only in template mode if (isTemplateMode) { - onChange(`\${${fullPath}}`, false); + onChange(`${fullPath}`, false); } else { onChange(fullPath, false); } } else if (needsTypeCasting) { // Wrap the variable in the document structure with or without interpolation based on mode - const wrappedValue = wrapInDocumentType(documentType, fullPath, isTemplateMode); + const wrappedValue = wrapInDocumentType(documentType, fullPath); onChange(wrappedValue, false); } else { // For other types (records, etc.), insert directly @@ -212,7 +211,7 @@ export const DocumentConfig = ({ onChange, onClose, targetLineRange, filteredCom return; } const isTemplateMode = inputMode === InputMode.TEMPLATE; - const wrappedValue = wrapInDocumentType(documentType, `"${url.trim()}"`, isTemplateMode); + const wrappedValue = wrapInDocumentType(documentType, `"${url.trim()}"`); onChange(wrappedValue, false, false); closeModal(POPUP_IDS.DOCUMENT_URL); }; From a18a690eac259bfa62cd2ef7d9e7981c2f4e4c2c Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Thu, 27 Nov 2025 12:09:55 +0530 Subject: [PATCH 5/8] remove invalid comments --- .../ChipExpressionEditor/components/ChipExpressionEditor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx index cdfd896e48..aecef38716 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx @@ -175,7 +175,6 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone const currentSelection = savedSelectionRef.current || view.state.selection.main; const { from, to } = options?.replaceFullText ? { from: 0, to: view.state.doc.length } : currentSelection; - // Only apply getHelperValue if selection is not on a token const selectionIsOnToken = isSelectionOnToken(currentSelection.from, currentSelection.to, view); const newValue = selectionIsOnToken ? value : configuration.getHelperValue(value); From 701e4bdf46037d885fa4285d7017c2e28fd0a156 Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Thu, 27 Nov 2025 13:02:23 +0530 Subject: [PATCH 6/8] fix chips not appearing in expression mode --- .../src/components/editors/ExpressionField.tsx | 2 +- .../MultiModeExpressionEditor/Configurations.tsx | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index 64ff92ba4f..e280ac64ff 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -136,7 +136,7 @@ export const ExpressionField: React.FC = ({ class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken): string { const isTextOrTemplateMode = primaryMode === InputMode.TEXT || primaryMode === InputMode.TEMPLATE; - if (isTextOrTemplateMode && token && token.type !== TokenType.FUNCTION ) { + if (isTextOrTemplateMode && (!token || token.type !== TokenType.FUNCTION) ) { return `\$\{${value}\}`; } return value; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx index 8f812bfdef..c1ac3bad7e 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx @@ -22,9 +22,8 @@ import { ParsedToken } from "./ChipExpressionEditor/utils"; export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken): string { - if (token?.type !== TokenType.FUNCTION) { - return `\$\{${value}\}`; - } + if (token?.type === TokenType.FUNCTION) return value; + return `\$\{${value}\}`; } getSerializationPrefix() { return "string `"; @@ -52,9 +51,8 @@ export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfi export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken): string { - if (token?.type !== TokenType.FUNCTION) { - return `\$\{${value}\}`; - } + if (token?.type === TokenType.FUNCTION) return value; + return `\$\{${value}\}`; } getSerializationPrefix() { return "`"; @@ -82,6 +80,7 @@ export class RawTemplateEditorConfig extends ChipExpressionEditorDefaultConfigur export class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken): string { - return value; + if (token?.type === TokenType.FUNCTION) return value; + return `\$\{${value}\}`; } } From 0b2a285bb08e8bdfa60c19daa5ab3a60149c5e98 Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Thu, 27 Nov 2025 13:29:20 +0530 Subject: [PATCH 7/8] Address PR cpmments --- .../src/components/editors/ExpressionField.tsx | 16 +++------------- .../components/ChipExpressionEditor.tsx | 6 +++--- .../Configurations.tsx | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index e280ac64ff..97ad848c59 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -33,8 +33,7 @@ import { HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig'; import RecordConfigPreviewEditor from './MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor'; -import { RawTemplateEditorConfig, StringTemplateEditorConfig } from './MultiModeExpressionEditor/Configurations'; -import { ParsedToken } from './MultiModeExpressionEditor/ChipExpressionEditor/utils'; +import { RawTemplateEditorConfig, StringTemplateEditorConfig, PrimaryModeChipExpressionEditorConfig } from './MultiModeExpressionEditor/Configurations'; export interface ExpressionField { inputMode: InputMode; @@ -133,16 +132,7 @@ export const ExpressionField: React.FC = ({ onOpenExpandedMode, isInExpandedMode }) => { - class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { - getHelperValue(value: string, token?: ParsedToken): string { - const isTextOrTemplateMode = primaryMode === InputMode.TEXT || primaryMode === InputMode.TEMPLATE; - if (isTextOrTemplateMode && (!token || token.type !== TokenType.FUNCTION) ) { - return `\$\{${value}\}`; - } - return value; - } - } - if (inputMode === InputMode.RECORD) { + if (inputMode === InputMode.RECORD) { return ( = ({ onOpenExpandedMode={onOpenExpandedMode} onRemove={onRemove} isInExpandedMode={isInExpandedMode} - configuration={new ChipExpressionEditorConfig()} + configuration={new PrimaryModeChipExpressionEditorConfig(primaryMode)} /> ); }; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx index aecef38716..1100735d86 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx @@ -345,10 +345,10 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone useEffect(() => { if (props.value == null || !viewRef.current) return; - const serializedValue = configuration.serializeValue(props.value.trim()); - const deserializeValue = configuration.deserializeValue(props.value.trim()); + const serializedValue = configuration.serializeValue(props.value); + const deserializeValue = configuration.deserializeValue(props.value); if (deserializeValue.trim() !== props.value.trim()) { - props.onChange(deserializeValue.trim(), deserializeValue.trim().length); + props.onChange(deserializeValue, deserializeValue.length); return } const updateEditorState = async () => { diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx index c1ac3bad7e..5b2647af38 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx @@ -19,6 +19,7 @@ import { ChipExpressionEditorDefaultConfiguration } from "./ChipExpressionEditor/ChipExpressionDefaultConfig"; import { TokenType } from "./ChipExpressionEditor/types"; import { ParsedToken } from "./ChipExpressionEditor/utils"; +import { InputMode } from "./ChipExpressionEditor/types"; export class StringTemplateEditorConfig extends ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken): string { @@ -84,3 +85,20 @@ export class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfi return `\$\{${value}\}`; } } + +export class PrimaryModeChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { + private readonly primaryMode: InputMode; + + constructor(primaryMode: InputMode) { + super(); + this.primaryMode = primaryMode; + } + + getHelperValue(value: string, token?: ParsedToken): string { + const isTextOrTemplateMode = this.primaryMode === InputMode.TEXT || this.primaryMode === InputMode.TEMPLATE; + if (isTextOrTemplateMode && (!token || token.type !== TokenType.FUNCTION)) { + return `\$\{${value}\}`; + } + return value; + } +} From ebaf3b9027fae08e4ba6de20313679ecfd61e9f1 Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Thu, 27 Nov 2025 13:32:57 +0530 Subject: [PATCH 8/8] Address PR comments --- .../ChipExpressionEditor/components/ChipExpressionEditor.tsx | 3 ++- .../TextExpressionEditor/TextModeEditor.tsx | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx index 1100735d86..bb86cee90d 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor.tsx @@ -364,8 +364,9 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone props.fileName, startLine !== undefined ? startLine : undefined ); - const prefixCorrectedTokenStream = tokenStream; + let prefixCorrectedTokenStream = tokenStream; if (tokenStream && tokenStream.length >= 5) { + prefixCorrectedTokenStream = [...tokenStream]; prefixCorrectedTokenStream[TOKEN_START_CHAR_OFFSET_INDEX] -= configuration.getSerializationPrefix().length; } setIsTokenUpdateScheduled(false); diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx index 553ce8fca2..d91d91d9e1 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/TextExpressionEditor/TextModeEditor.tsx @@ -16,7 +16,6 @@ * under the License. */ -import { ExpressionField } from "../../ExpressionField"; import React from "react"; import { getValueForTextModeEditor } from "../../utils"; import styled from "@emotion/styled"; @@ -38,8 +37,6 @@ const EditorContainer = styled.div` } `; -type TextModeEditorProps = Pick; - export const TextModeEditor: React.FC = (props) => { return (