diff --git a/workspaces/ballerina/ballerina-side-panel/package.json b/workspaces/ballerina/ballerina-side-panel/package.json index 495a762b3e2..70414674343 100644 --- a/workspaces/ballerina/ballerina-side-panel/package.json +++ b/workspaces/ballerina/ballerina-side-panel/package.json @@ -9,6 +9,7 @@ "types": "lib/index.d.ts", "scripts": { "build": "tsc --pretty && npm run copy:assets", + "test": "npm run build && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test test/typeCompletionUtils.test.mjs", "storybook": "start-storybook -p 6006", "watch": "tsc --pretty --watch", "build-storybook": "build-storybook", diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/index.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/index.ts index 27aa30c3e6e..475aa40d286 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/index.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/index.ts @@ -31,5 +31,6 @@ export * from "./SliderEditor"; export * from "./MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor"; export * from "./MultiModeExpressionEditor/Configurations"; export { getPropertyFromFormField } from "./utils"; +export { getTypeCompletionSearchText } from "./typeCompletionUtils"; export { InputMode } from "./MultiModeExpressionEditor/ChipExpressionEditor/types"; export { ExpandedEditor } from "./ExpandedEditor"; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/typeCompletionUtils.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/typeCompletionUtils.ts new file mode 100644 index 00000000000..2c6b52a03c9 --- /dev/null +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/typeCompletionUtils.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026, 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. + */ + +// Use only the active union member as the completion filter text. +// This is intentionally a delimiter-based heuristic, not a full type parser. +export function getTypeCompletionSearchText(value: string, cursorPosition: number): string { + const textBeforeCursor = value.slice(0, cursorPosition); + const activeUnionMember = textBeforeCursor.split('|').pop(); + + return activeUnionMember?.trim() ?? ""; +} diff --git a/workspaces/ballerina/ballerina-side-panel/test/typeCompletionUtils.test.mjs b/workspaces/ballerina/ballerina-side-panel/test/typeCompletionUtils.test.mjs new file mode 100644 index 00000000000..bb5fc65c018 --- /dev/null +++ b/workspaces/ballerina/ballerina-side-panel/test/typeCompletionUtils.test.mjs @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { getTypeCompletionSearchText } from "../lib/components/editors/typeCompletionUtils.js"; + +test("getTypeCompletionSearchText returns full text for a single type", () => { + assert.equal(getTypeCompletionSearchText("string", 6), "string"); +}); + +test("getTypeCompletionSearchText resets search text after union delimiter", () => { + assert.equal(getTypeCompletionSearchText("int|", 4), ""); +}); + +test("getTypeCompletionSearchText returns active union member", () => { + assert.equal(getTypeCompletionSearchText("int|str", 7), "str"); +}); + +test("getTypeCompletionSearchText trims spaces around active union member", () => { + assert.equal(getTypeCompletionSearchText("int | str", 9), "str"); +}); + +test("getTypeCompletionSearchText returns empty when cursor is immediately after the delimiter", () => { + assert.equal(getTypeCompletionSearchText("int|string", 4), ""); +}); + +test("getTypeCompletionSearchText returns empty for empty input", () => { + assert.equal(getTypeCompletionSearchText("", 0), ""); +}); + +test("getTypeCompletionSearchText returns empty when cursor is at start", () => { + assert.equal(getTypeCompletionSearchText("int", 0), ""); +}); + +test("getTypeCompletionSearchText returns last member of multi-member union", () => { + assert.equal(getTypeCompletionSearchText("int|float|str", 13), "str"); +}); + +test("getTypeCompletionSearchText returns active prefix when cursor is mid-member", () => { + assert.equal(getTypeCompletionSearchText("int|string", 7), "str"); +}); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/ArtifactForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/ArtifactForm/index.tsx index 868ff7e7a33..d5254ec8fba 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/ArtifactForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/ArtifactForm/index.tsx @@ -49,7 +49,8 @@ import { FormExpressionEditorProps, FormImports, HelperpaneOnChangeOptions, - InputMode + InputMode, + getTypeCompletionSearchText } from "@wso2/ballerina-side-panel"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { CompletionItem, FormExpressionEditorRef, HelperPaneHeight, Overlay, ThemeColors } from "@wso2/ui-toolkit"; @@ -623,7 +624,7 @@ export function ArtifactForm(props: ArtifactFormProps) { setTypes(visibleTypes); if (!fetchReferenceTypes) { - const effectiveText = value.slice(0, cursorPosition); + const effectiveText = getTypeCompletionSearchText(value, cursorPosition); let filteredTypes = visibleTypes.filter((type) => { const lowerCaseText = effectiveText.toLowerCase(); const lowerCaseLabel = type.label.toLowerCase(); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx index 118d5b93f03..380b1088b9b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FlowNodeForm/index.tsx @@ -58,6 +58,7 @@ import { HelperpaneOnChangeOptions, InputMode, ExpressionEditorDevantProps, + getTypeCompletionSearchText, } from "@wso2/ballerina-side-panel"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { @@ -924,7 +925,7 @@ export const FlowNodeForm = forwardRef { const lowerCaseText = effectiveText.toLowerCase(); const lowerCaseLabel = type.label.toLowerCase();