Skip to content

Commit 20f6525

Browse files
authored
Merge pull request #888 from senithkay/expression-editor-expanded-view
Expression editor expanded view
2 parents d088c1e + ec7eb56 commit 20f6525

File tree

12 files changed

+343
-101
lines changed

12 files changed

+343
-101
lines changed

workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/ExpandedEditor.tsx

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,39 @@
1919
import React, { useState, useEffect } from "react";
2020
import { createPortal } from "react-dom";
2121
import styled from "@emotion/styled";
22-
import { ThemeColors, Divider, Typography } from "@wso2/ui-toolkit";
23-
import { FormField } from "../../Form/types";
22+
import { ThemeColors, Divider, Typography, CompletionItem, FnSignatureDocumentation, HelperPaneHeight } from "@wso2/ui-toolkit";
23+
import { FormField, HelperpaneOnChangeOptions } from "../../Form/types";
2424
import { EditorMode } from "./modes/types";
2525
import { TextMode } from "./modes/TextMode";
2626
import { PromptMode } from "./modes/PromptMode";
27+
import { ExpressionMode } from "./modes/ExpressionMode";
2728
import { CompressButton } from "../MultiModeExpressionEditor/ChipExpressionEditor/components/FloatingButtonIcons";
29+
import { LineRange } from "@wso2/ballerina-core/lib/interfaces/common";
2830

2931
interface ExpandedPromptEditorProps {
3032
isOpen: boolean;
3133
field: FormField;
3234
value: string;
3335
onClose: () => void;
3436
onSave: (value: string) => void;
37+
onChange: (updatedValue: string, updatedCursorPosition: number) => void;
38+
// Optional mode override (if not provided, will be auto-detected)
39+
mode?: EditorMode;
40+
// Expression mode specific props
41+
completions?: CompletionItem[];
42+
fileName?: string;
43+
targetLineRange?: LineRange;
44+
extractArgsFromFunction?: (value: string, cursorPosition: number) => Promise<{
45+
label: string;
46+
args: string[];
47+
currentArgIndex: number;
48+
documentation?: FnSignatureDocumentation;
49+
}>;
50+
getHelperPane?: (
51+
value: string,
52+
onChange: (value: string, options?: HelperpaneOnChangeOptions) => void,
53+
helperPaneHeight: HelperPaneHeight
54+
) => React.ReactNode;
3555
}
3656

3757
const ModalContainer = styled.div`
@@ -89,35 +109,42 @@ const ModalContent = styled.div`
89109
*/
90110
const MODE_COMPONENTS: Record<EditorMode, React.ComponentType<any>> = {
91111
text: TextMode,
92-
prompt: PromptMode
112+
prompt: PromptMode,
113+
expression: ExpressionMode
93114
};
94115

95116
export const ExpandedEditor: React.FC<ExpandedPromptEditorProps> = ({
96117
isOpen,
97118
field,
98119
value,
99120
onClose,
121+
onChange,
100122
onSave,
123+
mode: propMode,
124+
completions,
125+
fileName,
126+
targetLineRange,
127+
extractArgsFromFunction,
128+
getHelperPane
101129
}) => {
102-
const [editedValue, setEditedValue] = useState(value);
103130
const promptFields = ["query", "instructions", "role"];
104-
const defaultMode: EditorMode = promptFields.includes(field.key) ? "prompt" : "text";
131+
132+
// Determine mode - use prop if provided, otherwise auto-detect
133+
const defaultMode: EditorMode = propMode ?? (
134+
promptFields.includes(field.key) ? "prompt" : "text"
135+
);
136+
105137
const [mode] = useState<EditorMode>(defaultMode);
106138
const [showPreview, setShowPreview] = useState(false);
107139
const [mouseDownTarget, setMouseDownTarget] = useState<EventTarget | null>(null);
108140

109-
useEffect(() => {
110-
setEditedValue(value);
111-
}, [value, isOpen]);
112-
113141
useEffect(() => {
114142
if (mode === "text") {
115143
setShowPreview(false);
116144
}
117145
}, [mode]);
118146

119147
const handleMinimize = () => {
120-
onSave(editedValue);
121148
onClose();
122149
};
123150

@@ -140,13 +167,21 @@ export const ExpandedEditor: React.FC<ExpandedPromptEditorProps> = ({
140167

141168
// Prepare props for the mode component
142169
const modeProps = {
143-
value: editedValue,
144-
onChange: setEditedValue,
170+
value: value,
171+
onChange: onChange,
145172
field,
146173
// Props for modes with preview support
147174
...(mode === "prompt" && {
148175
isPreviewMode: showPreview,
149176
onTogglePreview: () => setShowPreview(!showPreview)
177+
}),
178+
// Props for expression mode
179+
...(mode === "expression" && {
180+
completions,
181+
fileName,
182+
targetLineRange,
183+
extractArgsFromFunction,
184+
getHelperPane
150185
})
151186
};
152187

workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
*/
1818

1919
export { ExpandedEditor } from "./ExpandedEditor";
20-
export type { EditorMode, EditorModeProps, EditorModeWithPreviewProps } from "./modes/types";
20+
export type { EditorMode, EditorModeProps, EditorModeWithPreviewProps, EditorModeExpressionProps } from "./modes/types";
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import React from "react";
20+
import styled from "@emotion/styled";
21+
import { EditorModeExpressionProps } from "./types";
22+
import { ChipExpressionBaseComponent } from "../../MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionBaseComponent";
23+
24+
const ExpressionContainer = styled.div`
25+
width: 100%;
26+
height: 100%;
27+
display: flex;
28+
flex-direction: column;
29+
box-sizing: border-box;
30+
`;
31+
32+
/**
33+
* Expression mode editor - uses ChipExpressionBaseComponent in expanded mode
34+
*/
35+
export const ExpressionMode: React.FC<EditorModeExpressionProps> = ({
36+
value,
37+
onChange,
38+
completions = [],
39+
fileName,
40+
targetLineRange,
41+
extractArgsFromFunction,
42+
getHelperPane
43+
}) => {
44+
// Convert onChange signature from (value: string) => void to (value: string, cursorPosition: number) => void
45+
const handleChange = (updatedValue: string, updatedCursorPosition: number) => {
46+
onChange(updatedValue, updatedCursorPosition);
47+
};
48+
49+
return (
50+
<ExpressionContainer>
51+
<ChipExpressionBaseComponent
52+
value={value}
53+
onChange={handleChange}
54+
completions={completions}
55+
fileName={fileName}
56+
targetLineRange={targetLineRange}
57+
extractArgsFromFunction={extractArgsFromFunction}
58+
getHelperPane={getHelperPane}
59+
isInExpandedMode={true}
60+
/>
61+
</ExpressionContainer>
62+
);
63+
};

workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/PromptMode.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
7777
if (!content.trim()) {
7878
e.preventDefault();
7979
const newValue = textBeforeCursor.substring(0, lastNewlineIndex + 1) + '\n' + textAfterCursor;
80-
onChange(newValue);
80+
onChange(newValue, cursorPosition);
8181
// Set cursor position after both newlines
8282
queueMicrotask(() => {
8383
textarea.selectionStart = textarea.selectionEnd = lastNewlineIndex + 2;
@@ -88,7 +88,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
8888
// Continue the list
8989
e.preventDefault();
9090
const newValue = textBeforeCursor + '\n' + indent + marker + ' ' + textAfterCursor;
91-
onChange(newValue);
91+
onChange(newValue, cursorPosition);
9292
// Set cursor position after the list marker
9393
queueMicrotask(() => {
9494
const newCursorPos = cursorPosition + indent.length + marker.length + 2;
@@ -106,7 +106,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
106106
if (!content.trim()) {
107107
e.preventDefault();
108108
const newValue = textBeforeCursor.substring(0, lastNewlineIndex + 1) + '\n' + textAfterCursor;
109-
onChange(newValue);
109+
onChange(newValue, cursorPosition);
110110
// Set cursor position after both newlines
111111
queueMicrotask(() => {
112112
textarea.selectionStart = textarea.selectionEnd = lastNewlineIndex + 2;
@@ -118,7 +118,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
118118
e.preventDefault();
119119
const nextNumber = parseInt(number, 10) + 1;
120120
const newValue = textBeforeCursor + '\n' + indent + nextNumber + '. ' + textAfterCursor;
121-
onChange(newValue);
121+
onChange(newValue, cursorPosition);
122122
// Set cursor position after the list marker
123123
queueMicrotask(() => {
124124
const newCursorPos = cursorPosition + indent.length + nextNumber.toString().length + 3;
@@ -136,7 +136,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
136136
if (!content.trim()) {
137137
e.preventDefault();
138138
const newValue = textBeforeCursor.substring(0, lastNewlineIndex + 1) + '\n' + textAfterCursor;
139-
onChange(newValue);
139+
onChange(newValue, cursorPosition);
140140
// Set cursor position after both newlines
141141
queueMicrotask(() => {
142142
textarea.selectionStart = textarea.selectionEnd = lastNewlineIndex + 2;
@@ -147,7 +147,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
147147
// Continue the task list with unchecked box
148148
e.preventDefault();
149149
const newValue = textBeforeCursor + '\n' + indent + marker + ' [ ] ' + textAfterCursor;
150-
onChange(newValue);
150+
onChange(newValue, cursorPosition);
151151
// Set cursor position after the task marker
152152
queueMicrotask(() => {
153153
const newCursorPos = cursorPosition + indent.length + marker.length + 6;
@@ -174,7 +174,7 @@ export const PromptMode: React.FC<EditorModeWithPreviewProps> = ({
174174
<TextArea
175175
id={TEXTAREA_ID}
176176
value={value}
177-
onChange={(e) => onChange(e.target.value)}
177+
onChange={(e) => onChange(e.target.value, e.target.selectionStart)}
178178
onKeyDown={handleKeyDown}
179179
placeholder={placeholder}
180180
autoFocus

workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/TextMode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const TextMode: React.FC<EditorModeProps> = ({ value, onChange, field })
4848
return (
4949
<TextArea
5050
value={value}
51-
onChange={(e) => onChange(e.target.value)}
51+
onChange={(e) => onChange(e.target.value, e.target.selectionStart)}
5252
placeholder={field.placeholder || "Enter your text here..."}
5353
autoFocus
5454
/>

workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpandedEditor/modes/types.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
* under the License.
1717
*/
1818

19-
import { FormField } from "../../../Form/types";
19+
import { FormField, HelperpaneOnChangeOptions } from "../../../Form/types";
20+
import { CompletionItem, FnSignatureDocumentation, HelperPaneHeight } from "@wso2/ui-toolkit";
21+
import { LineRange } from "@wso2/ballerina-core/lib/interfaces/common";
2022

2123
/**
2224
* Base props that all editor mode components must implement
@@ -25,7 +27,7 @@ export interface EditorModeProps {
2527
/** Current value of the editor */
2628
value: string;
2729
/** Callback when value changes */
28-
onChange: (value: string) => void;
30+
onChange: (value: string, updatedCursorPosition: number) => void;
2931
/** Field metadata (for accessing field properties if needed) */
3032
field: FormField;
3133
}
@@ -40,15 +42,41 @@ export interface EditorModeWithPreviewProps extends EditorModeProps {
4042
onTogglePreview: (enabled: boolean) => void;
4143
}
4244

45+
/**
46+
* Extended props for expression mode with completions and helper pane support
47+
*/
48+
export interface EditorModeExpressionProps extends EditorModeProps {
49+
/** Completion items for autocomplete */
50+
completions?: CompletionItem[];
51+
/** File name for context */
52+
fileName?: string;
53+
/** Target line range for context */
54+
targetLineRange?: LineRange;
55+
/** Function to extract arguments from function calls */
56+
extractArgsFromFunction?: (value: string, cursorPosition: number) => Promise<{
57+
label: string;
58+
args: string[];
59+
currentArgIndex: number;
60+
documentation?: FnSignatureDocumentation;
61+
}>;
62+
/** Helper pane renderer function */
63+
getHelperPane?: (
64+
value: string,
65+
onChange: (value: string, options?: HelperpaneOnChangeOptions) => void,
66+
helperPaneHeight: HelperPaneHeight
67+
) => React.ReactNode;
68+
}
69+
4370
/**
4471
* Mode type identifier
4572
*/
46-
export type EditorMode = "text" | "prompt";
73+
export type EditorMode = "text" | "prompt" | "expression";
4774

4875
/**
4976
* Map of mode identifiers to their display labels
5077
*/
5178
export const MODE_LABELS: Record<EditorMode, string> = {
5279
text: "Text",
53-
prompt: "Prompt"
80+
prompt: "Prompt",
81+
expression: "Expression"
5482
};

0 commit comments

Comments
 (0)