diff --git a/.changeset/nasty-cats-provide.md b/.changeset/nasty-cats-provide.md new file mode 100644 index 000000000..09ee3d14e --- /dev/null +++ b/.changeset/nasty-cats-provide.md @@ -0,0 +1,6 @@ +--- +"@ensembleui/react-kitchen-sink": patch +"@ensembleui/react-runtime": patch +--- + +Keep selected options in search multiselect diff --git a/apps/kitchen-sink/src/ensemble/screens/help.yaml b/apps/kitchen-sink/src/ensemble/screens/help.yaml index e90cb49a7..2a639819a 100644 --- a/apps/kitchen-sink/src/ensemble/screens/help.yaml +++ b/apps/kitchen-sink/src/ensemble/screens/help.yaml @@ -1,4 +1,20 @@ View: + onLoad: + executeCode: | + ensemble.storage.set('testlistoptions', [ + { label: 'William Smith', value: 'william_smith' }, + { label: 'Evelyn Johnson', value: 'evelyn_johnson' }, + { label: 'Liam Brown', value: 'liam_brown' }, + { label: 'Bella Davis', value: 'bella_davis' }, + { label: 'James Wilson', value: 'james_wilson' }, + { label: 'Zachary Taylor', value: 'zachary_taylor' }, + { label: 'Nolan Martinez', value: 'nolan_martinez' }, + { label: 'Emma Thompson', value: 'emma_thompson' }, + { label: 'Oliver White', value: 'oliver_white' }, + { label: 'Sophia Lee', value: 'sophia_lee' }, + ]) + ensemble.storage.set('testoptions', []) + styles: scrollableView: true @@ -28,6 +44,31 @@ View: inputs: input1: "hello" input2: "world" + + - Button: + label: toggle modal + onTap: + showDialog: + options: + minWidth: 500px + body: + MultiSelect: + label: test + data: ${ensemble.storage.get('testoptions')} + labelKey: label + valueKey: value + value: ${ensemble.storage.get('testselect') || []} + onChange: + executeCode: | + ensemble.storage.set('testselect', option) + onSearch: + executeCode: | + const tempOptions = ensemble.storage.get('testlistoptions') + const filteredResult = tempOptions.filter(option => + option.label.toLowerCase().startsWith(search.toLowerCase()) + ) + ensemble.storage.set('testoptions', filteredResult) + - Text: styles: names: heading-1 @@ -75,3 +116,10 @@ View: styles: maxWidth: 500px source: https://img.huffingtonpost.com/asset/5ab4d4ac2000007d06eb2c56.jpeg?cache=sih0jwle4e&ops=1910_1000 + +API: + getProducts: + method: GET + inputs: + - search + uri: "https://dummyjson.com/users/search?q=${search}&limit=10" diff --git a/packages/runtime/src/widgets/Form/MultiSelect.tsx b/packages/runtime/src/widgets/Form/MultiSelect.tsx index e3aa55984..7f951e3df 100644 --- a/packages/runtime/src/widgets/Form/MultiSelect.tsx +++ b/packages/runtime/src/widgets/Form/MultiSelect.tsx @@ -22,6 +22,7 @@ import { isObject, isString, toNumber, + uniqBy, } from "lodash-es"; import { useDebounce } from "react-use"; import { WidgetRegistry } from "../../registry"; @@ -123,7 +124,7 @@ const MultiSelect: React.FC = (props) => { // load data and items useEffect(() => { - const tempOptions: MultiSelectOption[] = []; + const tempOptions: MultiSelectOption[] = values?.selectedValues || []; if (isArray(rawData)) { tempOptions.push( @@ -152,7 +153,9 @@ const MultiSelect: React.FC = (props) => { ); } - setOptions(tempOptions); + const uniqueOptions = uniqBy(tempOptions, "value"); + + setOptions(uniqueOptions); }, [rawData, values?.labelKey, values?.valueKey, values?.items]); // handle form instance diff --git a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx index d1c692922..4a1164b9c 100644 --- a/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx +++ b/packages/runtime/src/widgets/Form/__tests__/MultiSelect.test.tsx @@ -2,8 +2,30 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import userEvent from "@testing-library/user-event"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { type ReactNode } from "react"; +import { BrowserRouter } from "react-router-dom"; import { Form } from "../../index"; import { FormTestWrapper } from "./__shared__/fixtures"; +import { EnsembleScreen } from "../../../runtime/screen"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +interface BrowserRouterProps { + children: ReactNode; +} + +const BrowserRouterWrapper = ({ children }: BrowserRouterProps) => ( + + {children} + +); const defaultFormButton = [ { @@ -645,5 +667,145 @@ describe("MultiSelect Widget", () => { expect(screen.getByText("Anothe...")).toBeInTheDocument(); }); }); + + test.only("multiselect with search action", async () => { + render( + + option.label.toLowerCase().startsWith(search.toLowerCase()) + ) + ensemble.storage.set('test_options', filteredResult) + `, + }, + }, + }, + { + Button: { + label: "Close modal", + onTap: { + executeCode: "ensemble.closeAllDialogs()", + }, + }, + }, + ], + }, + }, + }, + }, + }, + }, + }} + />, + { wrapper: BrowserRouterWrapper }, + ); + + const option = { selector: ".ant-select-item-option-content" }; + const selected = { selector: ".ant-select-selection-item-content" }; + + userEvent.click(screen.getByText("Show Dialog")); + + await waitFor(() => { + expect(screen.getByText("This is modal")).toBeInTheDocument(); + }); + + userEvent.type(screen.getByRole("combobox"), "Bella"); + + await waitFor(() => { + expect(screen.getByText("Bella Davis", option)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByText("Bella Davis", option)); + + await waitFor(() => { + expect(screen.getByText("Bella Davis", selected)).toBeVisible(); + }); + + userEvent.click(screen.getByText("Close modal")); + + // Open 2nd time to check if the selected values are retained + + userEvent.click(screen.getByText("Show Dialog")); + + userEvent.type(screen.getByRole("combobox"), "Sophia"); + + await waitFor(() => { + expect(screen.getByText("Sophia Lee", option)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByText("Sophia Lee", option)); + + await waitFor(() => { + expect(screen.queryByText("Bella Davis", selected)).toBeVisible(); + expect(screen.queryByText("Sophia Lee", selected)).toBeVisible(); + }); + + userEvent.click(screen.getByText("Close modal")); + + // Open 3rd time to check if the selected values are retained + + userEvent.click(screen.getByText("Show Dialog")); + + await waitFor(() => { + expect( + screen.getByText("Bella Davis", { + selector: ".ant-select-selection-item-content", + }), + ).toBeInTheDocument(); + expect( + screen.getByText("Sophia Lee", { + selector: ".ant-select-selection-item-content", + }), + ).toBeInTheDocument(); + }); + }, 10000); }); /* eslint-enable react/no-children-prop */