diff --git a/integrations/standalone/tests/mock/master.spec.ts b/integrations/standalone/tests/mock/master.spec.ts index 7b3e0537..fd852af1 100644 --- a/integrations/standalone/tests/mock/master.spec.ts +++ b/integrations/standalone/tests/mock/master.spec.ts @@ -60,7 +60,7 @@ test.describe('add field', async () => { test('empty', async () => { await editor.add.open.locator.click(); await expect(editor.add.typeMessage.locator).toBeHidden(); - await editor.add.type.locator.clear(); + await editor.add.type.clear(); await editor.add.typeMessage.expectToHaveErrorMessage('Type cannot be empty.'); }); }); diff --git a/integrations/standalone/tests/pageobjects/AddFieldDialog.ts b/integrations/standalone/tests/pageobjects/AddFieldDialog.ts index 5eb46009..383987a2 100644 --- a/integrations/standalone/tests/pageobjects/AddFieldDialog.ts +++ b/integrations/standalone/tests/pageobjects/AddFieldDialog.ts @@ -2,13 +2,14 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { Button } from './abstract/Button'; import { FieldMessage } from './abstract/FieldMessage'; import { TextArea } from './abstract/TextArea'; +import { Combobox } from './abstract/Combobox'; export class AddFieldDialog { readonly locator: Locator; readonly open: Button; readonly name: TextArea; readonly nameMessage: FieldMessage; - readonly type: TextArea; + readonly type: Combobox; readonly typeMessage: FieldMessage; readonly create: Button; @@ -17,7 +18,7 @@ export class AddFieldDialog { this.open = new Button(page.locator('.master-content'), { name: 'Add field' }); this.name = new TextArea(this.locator, { label: 'Name' }); this.nameMessage = new FieldMessage(this.locator, { label: 'Name' }); - this.type = new TextArea(this.locator, { label: 'Type' }); + this.type = new Combobox(this.locator, { label: 'Type' }); this.typeMessage = new FieldMessage(this.locator, { label: 'Type' }); this.create = new Button(this.locator, { name: 'Create field' }); } diff --git a/integrations/standalone/tests/pageobjects/DataClassEditor.ts b/integrations/standalone/tests/pageobjects/DataClassEditor.ts index cbcaa850..4dc5f7ae 100644 --- a/integrations/standalone/tests/pageobjects/DataClassEditor.ts +++ b/integrations/standalone/tests/pageobjects/DataClassEditor.ts @@ -87,7 +87,7 @@ export class DataClassEditor { async addField(name: string, type: string) { await this.add.open.locator.click(); await this.add.name.locator.fill(name); - await this.add.type.locator.fill(type); + await this.add.type.fill(type); await this.add.create.locator.click(); } diff --git a/integrations/standalone/tests/pageobjects/abstract/Combobox.ts b/integrations/standalone/tests/pageobjects/abstract/Combobox.ts new file mode 100644 index 00000000..2df34ee6 --- /dev/null +++ b/integrations/standalone/tests/pageobjects/abstract/Combobox.ts @@ -0,0 +1,27 @@ +import { expect, type Locator } from '@playwright/test'; + +export class Combobox { + readonly locator: Locator; + + constructor(parentLocator: Locator, options?: { label?: string; nth?: number }) { + if (options?.label) { + this.locator = parentLocator.getByRole('combobox', { name: options.label, exact: true }); + } else { + this.locator = parentLocator.getByRole('combobox').nth(options?.nth ?? 0); + } + } + + async fill(value: string) { + await this.locator.fill(value); + await this.locator.blur(); + } + + async clear() { + await this.locator.fill(''); + await this.locator.blur(); + } + + async expectToHavePlaceholder(palceholder: string) { + await expect(this.locator).toHaveAttribute('placeholder', palceholder); + } +} diff --git a/package-lock.json b/package-lock.json index a212ddbe..f1a99aa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,17 +66,17 @@ "link": true }, "node_modules/@axonivy/jsonrpc": { - "version": "12.0.0-next.340", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/jsonrpc/-/jsonrpc-12.0.0-next.340.tgz", - "integrity": "sha512-Y+dy3Y8o5fu3yKZUiU1PrMbJdYt1wNBU8GFAMUcadlNNU53HLYo3youv34vZ0RKGyksvaiTo1egyIodBgMmcCQ==", + "version": "12.0.0-next.342", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/jsonrpc/-/jsonrpc-12.0.0-next.342.tgz", + "integrity": "sha512-hBwaxYEOQM9EuWtUey/9euK8shTmbVdK4TqGK5Y53JeYwHAZpiCdgvJq7hcLN8s1kC0nOmfgrcMn9iW79SMgaw==", "dependencies": { "vscode-jsonrpc": "^8.2.0" } }, "node_modules/@axonivy/ui-components": { - "version": "12.0.0-next.340", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-components/-/ui-components-12.0.0-next.340.tgz", - "integrity": "sha512-mzNUug2TKLEQSO0OE/8d1DZrgYqqHXMdRNY3BzR556549S1aVrKv/dWhqn+L9x646Bmzw1mDxmUgZR/Jun5TpQ==", + "version": "12.0.0-next.342", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-components/-/ui-components-12.0.0-next.342.tgz", + "integrity": "sha512-qTnzT7Jna4Ql5R7+mH0w/GVrD6UxFGT4iXcfjmmpYtm40CmcMk4hFFt2BIUZPOtCaw1zU6HY1Wjeok8Gx83bzw==", "dependencies": { "@radix-ui/react-accordion": "1.2.1", "@radix-ui/react-checkbox": "1.1.2", @@ -105,9 +105,9 @@ } }, "node_modules/@axonivy/ui-icons": { - "version": "12.0.0-next.340", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-icons/-/ui-icons-12.0.0-next.340.tgz", - "integrity": "sha512-LquDWh0+zYXhHbT2xbB3yBk0hHHnsYPMHnMsP/fT+D7e6/9aU0Qc/TKoZ4cATsxsLc3tErh3e8qRz2gTkyJzvw==" + "version": "12.0.0-next.342", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-icons/-/ui-icons-12.0.0-next.342.tgz", + "integrity": "sha512-FicD20p+T+RUvpCdTHqYAcqjJFSmty2pmoFZMoJMeDZQTpYKlwL7DoSIroV4JtMiC8jfaaq4kdXgJ33XLBE74Q==" }, "node_modules/@babel/code-frame": { "version": "7.24.7", @@ -15282,9 +15282,9 @@ "version": "12.0.0-next", "license": "Apache-2.0", "dependencies": { - "@axonivy/jsonrpc": "~12.0.0-next.340", - "@axonivy/ui-components": "~12.0.0-next.340", - "@axonivy/ui-icons": "~12.0.0-next.340", + "@axonivy/jsonrpc": "~12.0.0-next.342", + "@axonivy/ui-components": "~12.0.0-next.342", + "@axonivy/ui-icons": "~12.0.0-next.342", "@tanstack/react-query": "5.32.1", "@tanstack/react-query-devtools": "5.32.1", "react": "^18.2.0" diff --git a/packages/dataclass-editor/package.json b/packages/dataclass-editor/package.json index 3408d437..f6a2384e 100644 --- a/packages/dataclass-editor/package.json +++ b/packages/dataclass-editor/package.json @@ -17,9 +17,9 @@ "types": "lib/index.d.ts", "main": "lib/editor.js", "dependencies": { - "@axonivy/jsonrpc": "~12.0.0-next.340", - "@axonivy/ui-components": "~12.0.0-next.340", - "@axonivy/ui-icons": "~12.0.0-next.340", + "@axonivy/jsonrpc": "~12.0.0-next.342", + "@axonivy/ui-components": "~12.0.0-next.342", + "@axonivy/ui-icons": "~12.0.0-next.342", "@tanstack/react-query": "5.32.1", "@tanstack/react-query-devtools": "5.32.1", "react": "^18.2.0" diff --git a/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.css b/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.css new file mode 100644 index 00000000..1c5e47f8 --- /dev/null +++ b/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.css @@ -0,0 +1,3 @@ +.combobox-with-type-browser .ui-combobox { + flex: 1; +} diff --git a/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.tsx b/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.tsx new file mode 100644 index 00000000..4b30f5ed --- /dev/null +++ b/packages/dataclass-editor/src/detail/field/ComboboxFieldWithTypeBrowser.tsx @@ -0,0 +1,91 @@ +import { + BasicCheckbox, + BasicField, + Button, + Combobox, + Dialog, + DialogContent, + DialogTrigger, + Flex, + IvyIcon, + type BrowserNode, + type MessageData +} from '@axonivy/ui-components'; +import { IvyIcons } from '@axonivy/ui-icons'; +import { useMemo, useState } from 'react'; +import { Browser } from './browser/Browser'; +import { useAppContext } from '../../context/AppContext'; +import { useMeta } from '../../context/useMeta'; +import { typeData } from '../../data/type-data'; +import './ComboboxFieldWithTypeBrowser.css'; +import type { DataclassType } from '../../protocol/types'; + +export type InputFieldProps = { + value: string; + onChange: (value: string) => void; + message?: MessageData; +}; + +export const ComboboxFieldWithTypeBrowser = ({ value, onChange, message }: InputFieldProps) => { + const [open, setOpen] = useState(false); + const { context } = useAppContext(); + const [typeAsList, setTypeAsList] = useState(false); + const dataClasses = useMeta('meta/scripting/dataClasses', context, []).data; + const ivyTypes = useMeta('meta/scripting/ivyTypes', undefined, []).data; + const types = useMemo(() => typeData(dataClasses, ivyTypes, [], [], false), [dataClasses, ivyTypes]); + + const typeAsListChange = (change: boolean) => { + setTypeAsList(!typeAsList); + if (change && !(value.startsWith('List<') && value.endsWith('>'))) { + onChange(`List<${value}>`); + } else if (!change && value.startsWith('List<') && value.endsWith('>')) { + const removeListFromValue = value.slice(5, -1); + onChange(removeListFromValue); + } + }; + + const ExtendedComboboxItem = ({ icon, value, info }: BrowserNode) => ( + + + {value} + {info} + + ); + + return ( + + + + { + const foundDataclassType = dataClasses.find(type => type.name === value); + if (foundDataclassType) { + onChange(foundDataclassType.fullQualifiedName); + } else { + onChange(value); + } + }} + value={value} + options={types} + itemRender={option => } + /> + + + ); +}; diff --git a/packages/dataclass-editor/src/master/AddFieldDialog.tsx b/packages/dataclass-editor/src/master/AddFieldDialog.tsx index ef9e784d..7e45f116 100644 --- a/packages/dataclass-editor/src/master/AddFieldDialog.tsx +++ b/packages/dataclass-editor/src/master/AddFieldDialog.tsx @@ -25,7 +25,7 @@ import { useMemo, useRef, useState } from 'react'; import { useAppContext } from '../context/AppContext'; import type { DataClass, DataClassField } from '../data/dataclass'; import { isEntity } from '../data/dataclass-utils'; -import { InputFieldWithTypeBrowser } from '../detail/field/InputFieldWithTypeBrowser'; +import { ComboboxFieldWithTypeBrowser } from '../detail/field/ComboboxFieldWithTypeBrowser'; export const validateFieldName = (name: string, dataClass: DataClass): MessageData => { if (name.trim() === '') { @@ -118,13 +118,17 @@ export const AddFieldDialog = ({ table }: AddFieldDialogProps) => { New Attribute Choose the name and type of the attribute you want to add. -
event.preventDefault()}> + { + event.preventDefault(); + }} + > setName(event.target.value)} /> - +