From 15b26f823eb56b738a90572bfab6205640e55e9e Mon Sep 17 00:00:00 2001
From: Tolulope Adetula <31691737+Tooluloope@users.noreply.github.com>
Date: Fri, 17 Sep 2021 10:08:35 +0100
Subject: [PATCH] feat: Tree Select widget (#6271)
* feat: Tree Select
* feat: styling multiselect
* fix: selected values
* fix: remove console statement
* fix: popup position
* fix: selection types
* fix: Form validation using TreeSelect
* feat: Add Label to TreeSelect
* fix: styling
* fix: Dropdown search
* fix: Add Entity Definitions
* fix: Entity Definition
* Feat: Add clear icon
* fix: validation
* fix: options validation
* fix: Styling issues
* fix: build error
* Fix: Separate Tree Select widget
* fix: issues and add validation
* fix: Options Validation
* fix: issues with build
* fix: yarn
* fix: changes
* fix
* Fix: select component
* fix: PR issues
* fix: merge conflicts
* fix: issues
* fix: all issues
* test: added test
* fix: failing test
---
.../cypress/fixtures/TreeSelectDsl.json | 128 +++
.../DisplayWidgets/Tree_Select_spec.js | 28 +
.../FormWidgets/Multi_Select_Tree_spec.js | 47 +
.../FormWidgets/Single_Select_Tree_spec.js | 47 +
app/client/cypress/locators/FormWidgets.json | 3 +
.../cypress/locators/publishWidgetspage.json | 2 +
app/client/cypress/support/commands.js | 8 +
app/client/package.json | 1 +
.../assets/icons/widget/multi-tree-select.svg | 1 +
.../icons/widget/single-tree-select.svg | 1 +
app/client/src/constants/HelpConstants.ts | 8 +
app/client/src/constants/WidgetValidation.ts | 1 +
app/client/src/icons/WidgetIcons.tsx | 12 +
app/client/src/utils/WidgetRegistry.tsx | 9 +
.../utils/autocomplete/EntityDefinitions.ts | 39 +
app/client/src/utils/validation/common.ts | 1 +
.../MenuButtonWidget/component/index.tsx | 1 -
.../component/index.styled.tsx | 891 ++++++++++++++++++
.../MultiSelectTreeWidget/component/index.tsx | 191 ++++
.../widgets/MultiSelectTreeWidget/icon.svg | 1 +
.../widgets/MultiSelectTreeWidget/index.ts | 51 +
.../MultiSelectTreeWidget/widget/index.tsx | 465 +++++++++
.../MultiSelectWidget/component/index.tsx | 6 +-
.../component/index.styled.tsx | 888 +++++++++++++++++
.../component/index.tsx | 182 ++++
.../widgets/SingleSelectTreeWidget/icon.svg | 1 +
.../widgets/SingleSelectTreeWidget/index.ts | 50 +
.../SingleSelectTreeWidget/widget/index.tsx | 425 +++++++++
app/client/src/workers/validations.ts | 47 +
.../test/__mocks__/RealmExecutorMock.ts | 2 +-
.../factories/Widgets/ContainerFactory.ts | 2 +-
.../factories/Widgets/RadiogroupFactory.ts | 54 +-
.../test/factories/Widgets/TextFactory.ts | 2 +-
app/client/test/testMockedWidgets.tsx | 6 +-
app/client/yarn.lock | 28 +-
35 files changed, 3590 insertions(+), 39 deletions(-)
create mode 100644 app/client/cypress/fixtures/TreeSelectDsl.json
create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Tree_Select_spec.js
create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js
create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js
create mode 100644 app/client/src/assets/icons/widget/multi-tree-select.svg
create mode 100644 app/client/src/assets/icons/widget/single-tree-select.svg
create mode 100644 app/client/src/widgets/MultiSelectTreeWidget/component/index.styled.tsx
create mode 100644 app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx
create mode 100644 app/client/src/widgets/MultiSelectTreeWidget/icon.svg
create mode 100644 app/client/src/widgets/MultiSelectTreeWidget/index.ts
create mode 100644 app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx
create mode 100644 app/client/src/widgets/SingleSelectTreeWidget/component/index.styled.tsx
create mode 100644 app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx
create mode 100644 app/client/src/widgets/SingleSelectTreeWidget/icon.svg
create mode 100644 app/client/src/widgets/SingleSelectTreeWidget/index.ts
create mode 100644 app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx
diff --git a/app/client/cypress/fixtures/TreeSelectDsl.json b/app/client/cypress/fixtures/TreeSelectDsl.json
new file mode 100644
index 0000000000..edc5109c2c
--- /dev/null
+++ b/app/client/cypress/fixtures/TreeSelectDsl.json
@@ -0,0 +1,128 @@
+{
+ "dsl":{
+ "widgetName":"MainContainer",
+ "backgroundColor":"none",
+ "rightColumn":1034.3999999999999,
+ "snapColumns":64,
+ "detachFromLayout":true,
+ "widgetId":"0",
+ "topRow":0,
+ "bottomRow":1990,
+ "containerStyle":"none",
+ "snapRows":125,
+ "parentRowSpace":1,
+ "type":"CANVAS_WIDGET",
+ "canExtend":true,
+ "version":38,
+ "minHeight":2000,
+ "parentColumnSpace":1,
+ "dynamicTriggerPathList":[],
+ "dynamicBindingPathList":[],
+ "leftColumn":0,
+ "children":[
+ {
+ "widgetName":"MultiSelectTree1",
+ "displayName":"Multi Select Tree",
+ "iconSVG":"/static/media/icon.f264210c.svg",
+ "labelText":"Label",
+ "topRow":38,
+ "bottomRow":44.88,
+ "parentRowSpace":10,
+ "type":"MULTI_SELECT_TREE_WIDGET",
+ "hideCard":false,
+ "mode":"SHOW_ALL",
+ "defaultOptionValue":[],
+ "parentColumnSpace":15.974999999999998,
+ "leftColumn":18,
+ "options":[
+ {
+ "label":"Blue",
+ "value":"BLUE",
+ "children":[
+ {
+ "label":"Dark Blue",
+ "value":"DARK BLUE"
+ },
+ {
+ "label":"Light Blue",
+ "value":"LIGHT BLUE"
+ }
+ ]
+ },
+ {
+ "label":"Green",
+ "value":"GREEN"
+ },
+ {
+ "label":"Red",
+ "value":"RED"
+ }
+ ],
+ "placeholderText":"select option(s)",
+ "isDisabled":false,
+ "key":"1zu067mn51",
+ "isRequired":false,
+ "rightColumn":34,
+ "widgetId":"zvm3vcs5gp",
+ "isVisible":true,
+ "version":1,
+ "expandAll":false,
+ "parentId":"0",
+ "renderMode":"CANVAS",
+ "isLoading":false,
+ "allowClear":false
+ },
+ {
+ "widgetName":"SingleSelectTree1",
+ "displayName":"Single Select Tree",
+ "iconSVG":"/static/media/icon.f815ebe3.svg",
+ "labelText":"Label",
+ "topRow":58,
+ "bottomRow":64.8,
+ "parentRowSpace":10,
+ "type":"SINGLE_SELECT_TREE_WIDGET",
+ "hideCard":false,
+ "defaultOptionValue":"BLUE",
+ "parentColumnSpace":15.974999999999998,
+ "leftColumn":17,
+ "options":[
+ {
+ "label":"Blue",
+ "value":"BLUE",
+ "children":[
+ {
+ "label":"Dark Blue",
+ "value":"DARK BLUE"
+ },
+ {
+ "label":"Light Blue",
+ "value":"LIGHT BLUE"
+ }
+ ]
+ },
+ {
+ "label":"Green",
+ "value":"GREEN"
+ },
+ {
+ "label":"Red",
+ "value":"RED"
+ }
+ ],
+ "placeholderText":"select option",
+ "isDisabled":false,
+ "key":"cul8w70bzs",
+ "isRequired":false,
+ "rightColumn":33,
+ "widgetId":"0zloh94nd4",
+ "isVisible":true,
+ "version":1,
+ "expandAll":false,
+ "parentId":"0",
+ "renderMode":"CANVAS",
+ "isLoading":false,
+ "allowClear":false
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Tree_Select_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Tree_Select_spec.js
new file mode 100644
index 0000000000..1954d220ea
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Tree_Select_spec.js
@@ -0,0 +1,28 @@
+const dsl = require("../../../../fixtures/TreeSelectDsl.json");
+
+describe("Tree Select Widget", function() {
+ beforeEach(() => {
+ cy.addDsl(dsl);
+ });
+
+ it("Open Existing MultiSelectTree from created Widgets list", () => {
+ cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({
+ multiple: true,
+ });
+ cy.get(
+ ".bp3-icon-caret-right ~ .t--entity-name:contains(MultiSelectTree1)",
+ ).click({
+ multiple: true,
+ });
+ });
+ it("Open Existing SingleSelectTree from created Widgets list", () => {
+ cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({
+ multiple: true,
+ });
+ cy.get(
+ ".bp3-icon-caret-right ~ .t--entity-name:contains(SingleSelectTree1)",
+ ).click({
+ multiple: true,
+ });
+ });
+});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js
new file mode 100644
index 0000000000..85bc985397
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Multi_Select_Tree_spec.js
@@ -0,0 +1,47 @@
+const dsl = require("../../../../fixtures/TreeSelectDsl.json");
+const formWidgetsPage = require("../../../../locators/FormWidgets.json");
+const publish = require("../../../../locators/publishWidgetspage.json");
+const commonlocators = require("../../../../locators/commonlocators.json");
+
+describe("MultiSelectTree Widget Functionality", function() {
+ before(() => {
+ cy.addDsl(dsl);
+ });
+ it("Selects value with enter in default value", () => {
+ cy.openPropertyPane("multiselecttreewidget");
+ cy.testJsontext("defaultvalue", "RED\n");
+ cy.get(formWidgetsPage.multiselecttreeWidget)
+ .find(".rc-tree-select-selection-item-content")
+ .first()
+ .should("have.text", "Red");
+ });
+ it(" To Validate Options", function() {
+ cy.get(formWidgetsPage.treeSelectInput)
+ .first()
+ .click({ force: true });
+ cy.get(formWidgetsPage.treeSelectInput)
+ .first()
+ .type("light");
+ cy.treeSelectDropdown("Light Blue");
+ });
+ it("To Unchecked Visible Widget", function() {
+ cy.togglebarDisable(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ cy.get(
+ publish.multiselecttreewidget + " " + ".rc-tree-select-multiple",
+ ).should("not.exist");
+ cy.get(publish.backToEditor).click();
+ });
+ it(" To Check Visible Widget", function() {
+ cy.openPropertyPane("multiselecttreewidget");
+ cy.togglebar(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ cy.get(
+ publish.multiselecttreewidget + " " + ".rc-tree-select-multiple",
+ ).should("be.visible");
+ cy.get(publish.backToEditor).click();
+ });
+});
+afterEach(() => {
+ // put your clean up code if any
+});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js
new file mode 100644
index 0000000000..355c946203
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Single_Select_Tree_spec.js
@@ -0,0 +1,47 @@
+const dsl = require("../../../../fixtures/TreeSelectDsl.json");
+const formWidgetsPage = require("../../../../locators/FormWidgets.json");
+const publish = require("../../../../locators/publishWidgetspage.json");
+const commonlocators = require("../../../../locators/commonlocators.json");
+
+describe("MultiSelectTree Widget Functionality", function() {
+ before(() => {
+ cy.addDsl(dsl);
+ });
+ it("Selects value with enter in default value", () => {
+ cy.openPropertyPane("singleselecttreewidget");
+ cy.testJsontext("defaultvalue", "RED\n");
+ cy.get(formWidgetsPage.singleselecttreeWidget)
+ .find(".rc-tree-select-selection-item")
+ .first()
+ .should("have.text", "Red");
+ });
+ it(" To Validate Options", function() {
+ cy.get(formWidgetsPage.treeSelectInput)
+ .last()
+ .click({ force: true });
+ cy.get(formWidgetsPage.treeSelectInput)
+ .last()
+ .type("light");
+ cy.treeSelectDropdown("Light Blue");
+ });
+ it("To Unchecked Visible Widget", function() {
+ cy.togglebarDisable(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ cy.get(
+ publish.singleselecttreewidget + " " + ".rc-tree-select-single",
+ ).should("not.exist");
+ cy.get(publish.backToEditor).click();
+ });
+ it(" To Check Visible Widget", function() {
+ cy.openPropertyPane("singleselecttreewidget");
+ cy.togglebar(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ cy.get(
+ publish.singleselecttreewidget + " " + ".rc-tree-select-single",
+ ).should("be.visible");
+ cy.get(publish.backToEditor).click();
+ });
+});
+afterEach(() => {
+ // put your clean up code if any
+});
diff --git a/app/client/cypress/locators/FormWidgets.json b/app/client/cypress/locators/FormWidgets.json
index a839156cf7..b7a5f1ae04 100644
--- a/app/client/cypress/locators/FormWidgets.json
+++ b/app/client/cypress/locators/FormWidgets.json
@@ -2,6 +2,8 @@
"checkboxWidget": ".t--draggable-checkboxwidget",
"dropdownWidget": ".t--draggable-dropdownwidget",
"multiselectWidget": ".t--draggable-multiselectwidget",
+ "multiselecttreeWidget": ".t--draggable-multiselecttreewidget",
+ "singleselecttreeWidget": ".t--draggable-singleselecttreewidget",
"dropdownSelectionType": ".t--property-control-selectiontype .bp3-popover-target",
"radioWidget": ".t--draggable-radiogroupwidget",
"checkboxGroupWidget": ".t--draggable-checkboxgroupwidget",
@@ -22,6 +24,7 @@
"labelvalue": ".t--draggable-dropdownwidget label",
"dropdownInput": ".bp3-tag-input-values",
"mulitiselectInput": ".rc-select-selection-search-input",
+ "treeSelectInput": ".rc-tree-select-selection-search-input",
"labelradio": ".t--draggable-radiogroupwidget label",
"labelCheckboxGroup": ".t--draggable-checkboxgroupwidget label",
"deleteradiovalue": ".t--property-control-options mask",
diff --git a/app/client/cypress/locators/publishWidgetspage.json b/app/client/cypress/locators/publishWidgetspage.json
index c57574f84f..35e419820d 100644
--- a/app/client/cypress/locators/publishWidgetspage.json
+++ b/app/client/cypress/locators/publishWidgetspage.json
@@ -13,6 +13,8 @@
"imageWidget": ".t--widget-imagewidget",
"dropdownWidget": ".t--widget-dropdownwidget",
"multiselectwidget": ".t--widget-multiselectwidget",
+ "multiselecttreewidget": ".t--widget-multiselecttreewidget",
+ "singleselecttreewidget": ".t--widget-singleselecttreewidget",
"tabWidget": ".t--widget-tabswidget",
"chartWidget": ".t--widget-chartwidget",
"horizontalTab": ".t--widget-chartwidget g[class*='-scrollContainer'] rect",
diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js
index dfec7c649f..d9420a1fb9 100644
--- a/app/client/cypress/support/commands.js
+++ b/app/client/cypress/support/commands.js
@@ -1805,6 +1805,14 @@ Cypress.Commands.add("dropdownMultiSelectDynamic", (text) => {
.click({ force: true })
.should("have.text", text);
});
+Cypress.Commands.add("treeSelectDropdown", (text) => {
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
+ cy.wait(2000);
+ cy.get(".tree-select-dropdown")
+ .contains(text)
+ .click({ force: true })
+ .should("have.text", text);
+});
Cypress.Commands.add("dropdownDynamicUpdated", (text) => {
// eslint-disable-next-line cypress/no-unnecessary-waiting
diff --git a/app/client/package.json b/app/client/package.json
index e47c2144de..1e82d2e267 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -103,6 +103,7 @@
"prismjs": "^1.24.0",
"rc-pagination": "^3.1.3",
"rc-select": "^12.1.10",
+ "rc-tree-select": "^4.4.0-alpha.2",
"re-reselect": "^3.4.0",
"react": "^16.12.0",
"react-base-table": "^1.9.1",
diff --git a/app/client/src/assets/icons/widget/multi-tree-select.svg b/app/client/src/assets/icons/widget/multi-tree-select.svg
new file mode 100644
index 0000000000..181e4e55cc
--- /dev/null
+++ b/app/client/src/assets/icons/widget/multi-tree-select.svg
@@ -0,0 +1 @@
+
diff --git a/app/client/src/assets/icons/widget/single-tree-select.svg b/app/client/src/assets/icons/widget/single-tree-select.svg
new file mode 100644
index 0000000000..dc9ccf2f42
--- /dev/null
+++ b/app/client/src/assets/icons/widget/single-tree-select.svg
@@ -0,0 +1 @@
+
diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts
index 26a0b0a5b3..46083cb65c 100644
--- a/app/client/src/constants/HelpConstants.ts
+++ b/app/client/src/constants/HelpConstants.ts
@@ -139,6 +139,14 @@ export const HelpMap: Record = {
path: "/widget-reference/menu-button",
searchKey: "Menu Button",
},
+ TREE_MULTI_SELECT_WIDGET: {
+ path: "/widget-reference/tree-multi-select",
+ searchKey: "Tree Multi Select",
+ },
+ TREE_SINGLE_SELECT_WIDGET: {
+ path: "/widget-reference/tree-single-select",
+ searchKey: "Tree Single Select",
+ },
ICON_BUTTON_WIDGET: {
path: "/widget-reference/icon-button",
searchKey: "Icon Button",
diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts
index d740138d7c..bbd1dbe231 100644
--- a/app/client/src/constants/WidgetValidation.ts
+++ b/app/client/src/constants/WidgetValidation.ts
@@ -10,6 +10,7 @@ export enum ValidationTypes {
OBJECT = "OBJECT",
ARRAY = "ARRAY",
OBJECT_ARRAY = "OBJECT_ARRAY",
+ NESTED_OBJECT_ARRAY = "NESTED_OBJECT_ARRAY",
DATE_ISO_STRING = "DATE_ISO_STRING",
IMAGE_URL = "IMAGE_URL",
FUNCTION = "FUNCTION",
diff --git a/app/client/src/icons/WidgetIcons.tsx b/app/client/src/icons/WidgetIcons.tsx
index 1b198d5d29..895cf6943b 100644
--- a/app/client/src/icons/WidgetIcons.tsx
+++ b/app/client/src/icons/WidgetIcons.tsx
@@ -27,6 +27,8 @@ import { ReactComponent as RatingIcon } from "assets/icons/widget/rating.svg";
import { ReactComponent as EmbedIcon } from "assets/icons/widget/embed.svg";
import { ReactComponent as DividerIcon } from "assets/icons/widget/divider.svg";
import { ReactComponent as MenuButtonIcon } from "assets/icons/widget/menu-button.svg";
+import { ReactComponent as MultiTreeSelectIcon } from "assets/icons/widget/multi-tree-select.svg";
+import { ReactComponent as SingleTreeSelectIcon } from "assets/icons/widget/single-tree-select.svg";
import { ReactComponent as IconButtonIcon } from "assets/icons/widget/icon-button.svg";
import { ReactComponent as StatboxIcon } from "assets/icons/widget/statbox.svg";
import { ReactComponent as CheckboxGroupIcon } from "assets/icons/widget/checkbox-group.svg";
@@ -177,6 +179,16 @@ export const WidgetIcons: {
),
+ TREE_SINGLE_SELECT_WIDGET: (props: IconProps) => (
+
+
+
+ ),
+ TREE_MULTI_SELECT_WIDGET: (props: IconProps) => (
+
+
+
+ ),
ICON_BUTTON_WIDGET: (props: IconProps) => (
diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx
index 17ac4be9ae..f830e15467 100644
--- a/app/client/src/utils/WidgetRegistry.tsx
+++ b/app/client/src/utils/WidgetRegistry.tsx
@@ -96,6 +96,12 @@ import AudioRecorderWidget, {
} from "widgets/AudioRecorderWidget";
import log from "loglevel";
+import SingleSelectTreeWidget, {
+ CONFIG as SINGLE_SELECT_TREE_WIDGET_CONFIG,
+} from "widgets/SingleSelectTreeWidget";
+import MultiSelectTreeWidget, {
+ CONFIG as MULTI_SELECT_TREE_WIDGET_CONFIG,
+} from "widgets/MultiSelectTreeWidget";
export const registerWidgets = () => {
const start = performance.now();
@@ -135,5 +141,8 @@ export const registerWidgets = () => {
registerWidget(FilePickerWidgetV2, FILEPICKER_WIDGET_V2_CONFIG);
registerWidget(StatboxWidget, STATBOX_WIDGET_CONFIG);
registerWidget(AudioRecorderWidget, AUDIO_RECORDER_WIDGET_CONFIG);
+ registerWidget(MultiSelectTreeWidget, MULTI_SELECT_TREE_WIDGET_CONFIG);
+ registerWidget(SingleSelectTreeWidget, SINGLE_SELECT_TREE_WIDGET_CONFIG);
+
log.debug("Widget registration took: ", performance.now() - start, "ms");
};
diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts
index fac3797279..0216ee6024 100644
--- a/app/client/src/utils/autocomplete/EntityDefinitions.ts
+++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts
@@ -314,6 +314,45 @@ export const entityDefinitions: Record = {
isVisible: isVisible,
label: "string",
},
+ //TODO: fix this after development
+ SINGLE_SELECT_TREE_WIDGET: {
+ "!doc":
+ "Single Select Tree is used to capture user input from a specified list of permitted inputs/Nested Inputs.",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ isVisible: isVisible,
+ selectedOptionValue: {
+ "!type": "string",
+ "!doc": "The value selected in a tree select dropdown",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ },
+ selectedOptionLabel: {
+ "!type": "string",
+ "!doc": "The selected option label in a tree select dropdown",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ },
+ isDisabled: "bool",
+ isValid: "bool",
+ options: "[dropdownOption]",
+ },
+ MULTI_SELECT_TREE_WIDGET: {
+ "!doc":
+ "Multi Select Tree is used to capture user inputs from a specified list of permitted inputs/Nested Inputs. A Tree Select can capture a single choice as well as multiple choices",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ isVisible: isVisible,
+ selectedOptionValues: {
+ "!type": "[string]",
+ "!doc": "The array of values selected in a tree select dropdown",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ },
+ selectedOptionLabels: {
+ "!type": "[string]",
+ "!doc": "The array of selected option labels in a tree select dropdown",
+ "!url": "https://docs.appsmith.com/widget-reference/treeselect",
+ },
+ isDisabled: "bool",
+ isValid: "bool",
+ options: "[dropdownOption]",
+ },
ICON_BUTTON_WIDGET: {
"!doc":
"Icon button widget is just an icon, along with all other button properties.",
diff --git a/app/client/src/utils/validation/common.ts b/app/client/src/utils/validation/common.ts
index 30794e98c3..ffad3f8eda 100644
--- a/app/client/src/utils/validation/common.ts
+++ b/app/client/src/utils/validation/common.ts
@@ -114,6 +114,7 @@ export function getExpectedValue(
autocompleteDataType: AutocompleteDataType.OBJECT,
};
case ValidationTypes.ARRAY:
+ case ValidationTypes.NESTED_OBJECT_ARRAY:
if (config.params?.allowedValues) {
const allowed = config.params?.allowedValues.join("' | '");
return {
diff --git a/app/client/src/widgets/MenuButtonWidget/component/index.tsx b/app/client/src/widgets/MenuButtonWidget/component/index.tsx
index 4c30371681..5940ca2222 100644
--- a/app/client/src/widgets/MenuButtonWidget/component/index.tsx
+++ b/app/client/src/widgets/MenuButtonWidget/component/index.tsx
@@ -272,7 +272,6 @@ const BaseButton = styled(Button)`
}
`}
-
border-radius: ${({ borderRadius }) =>
borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};
diff --git a/app/client/src/widgets/MultiSelectTreeWidget/component/index.styled.tsx b/app/client/src/widgets/MultiSelectTreeWidget/component/index.styled.tsx
new file mode 100644
index 0000000000..cb8ec9c21b
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectTreeWidget/component/index.styled.tsx
@@ -0,0 +1,891 @@
+import React from "react";
+import { Checkbox, Classes, Label } from "@blueprintjs/core";
+import styled, { keyframes } from "styled-components";
+import { Colors } from "constants/Colors";
+import { createGlobalStyle } from "constants/DefaultTheme";
+import {
+ FontStyleTypes,
+ TextSize,
+ TEXT_SIZES,
+} from "constants/WidgetConstants";
+
+export const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
+ return ;
+};
+
+export const TextLabelWrapper = styled.div<{
+ compactMode: boolean;
+}>`
+ ${(props) =>
+ props.compactMode ? "&&& {margin-right: 5px;}" : "width: 100%;"}
+ display: flex;
+`;
+
+export const StyledLabel = styled(Label)<{
+ $compactMode: boolean;
+ $labelText?: string;
+ $labelTextColor?: string;
+ $labelTextSize?: TextSize;
+ $labelStyle?: string;
+}>`
+ overflow-y: hidden;
+ text-overflow: ellipsis;
+ width: ${(props) => (props.$compactMode ? "auto" : "100%")};
+ text-align: left;
+ color: ${(props) => props.$labelTextColor || "inherit"};
+ font-size: ${(props) =>
+ props.$labelTextSize ? TEXT_SIZES[props.$labelTextSize] : "14px"};
+ font-weight: ${(props) =>
+ props?.$labelStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
+ font-style: ${(props) =>
+ props?.$labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
+`;
+
+const rcSelectDropdownSlideUpIn = keyframes`
+ 0% {
+ opacity: 0;
+ transform-origin: 0% 0%;
+ }
+ 100% {
+ opacity: 1;
+ transform-origin: 0% 0%;
+ }
+`;
+
+const rcSelectDropdownSlideUpOut = keyframes`
+ 0% {
+ opacity: 1;
+ transform-origin: 0% 0%;
+ }
+100% {
+ opacity: 0;
+ transform-origin: 0% 0%;
+ }
+`;
+
+export const DropdownStyles = createGlobalStyle`
+.rc-tree-select-dropdown-hidden {
+ display: none;
+}
+.rc-tree-select-item-group {
+ color: #999;
+ font-weight: bold;
+ font-size: 80%;
+}
+.rc-tree-select-item-option {
+ position: relative;
+ display: flex;
+
+ flex-direction: row-reverse;
+ .rc-tree-select-item-option-state {
+ pointer-events: all;
+ margin-right: 10px;
+ }
+}
+.rc-tree-select-item-option-grouped {
+ padding-left: 24px;
+}
+.rc-tree-select-item-option-content {
+ flex: 1 1 0;
+}
+.rc-tree-select-item-option-active {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-item-option-selected {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-item-option-disabled {
+ color: #999;
+}
+.rc-tree-select-item-empty {
+ text-align: center;
+ color: #999;
+}
+.rc-tree-select-selection__choice-zoom {
+ transition: all 0s;
+}
+.rc-tree-select-selection__choice-zoom-appear {
+ opacity: 0;
+}
+.rc-tree-select-selection__choice-zoom-appear.rc-tree-select-selection__choice-zoom-appear-active {
+ opacity: 1;
+}
+.rc-tree-select-selection__choice-zoom-leave {
+ opacity: 1;
+}
+.rc-tree-select-selection__choice-zoom-leave.rc-tree-select-selection__choice-zoom-leave-active {
+ opacity: 0;
+}
+.rc-tree-select-dropdown-slide-up-enter {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-appear {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-leave {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 1;
+ animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name:${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name: ${rcSelectDropdownSlideUpOut};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpOut};
+ animation-play-state: running;
+}
+
+
+
+
+.tree-select-dropdown.single-tree-select-dropdown {
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode.rc-tree-select-tree-treenode-disabled
+ span.rc-tree-select-tree-iconEle {
+cursor: not-allowed;
+ }
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ position: relative;
+ cursor: pointer;
+ margin-left: 5px;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ direction: ltr;
+ background-color: #fff;
+border: 1px solid #E8E8E8;
+ border-radius: 100%;
+ border-collapse: separate;
+ transition: all .3s;
+ :after{
+ position: absolute;
+ top: 50%;
+ left: 52%;
+ display: table;
+ width: 10px;
+ height: 10px;
+ border: none;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+ 45deg
+ ) scale(0) translate(-50%,-50%);
+ opacity: 0;
+ transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
+ content: " ";
+ }
+
+ }
+
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-node-selected
+ span.rc-tree-select-tree-iconEle {
+ :after{
+ width: 10px;
+ height: 10px;
+ transform: translate(-50%,-50%) scale(1);
+ background: rgb(3, 179, 101) !important;
+ opacity: 1;
+ content: " ";
+ border-radius: 100%;
+
+ }
+ }
+
+}
+.tree-select-dropdown {
+ min-height: 100px;
+ min-width: 250px !important;
+ position: absolute;
+ background: #fff;
+ width: 100%;
+ border-radius: 0px;
+ margin-top: 10px;
+ padding: 12px;
+ background: white;
+ box-shadow: 0 0 2px rgb(0 0 0 / 20%) !important;
+ &&&& .${Classes.ALIGN_LEFT} {
+ font-size: 16px;
+ padding-bottom: 10px;
+ margin-left: 16px ;
+ .${Classes.CONTROL_INDICATOR} {
+ margin-right: 20px;
+ }
+ }
+ &&&& .${Classes.CONTROL} .${Classes.CONTROL_INDICATOR} {
+ background: white;
+ box-shadow: none;
+ border-width: 2px;
+ border-style: solid;
+ border-color: ${Colors.GEYSER};
+ &::before {
+ width: auto;
+ height: 1em;
+ }
+ }
+ .${Classes.CONTROL} input:checked ~ .${Classes.CONTROL_INDICATOR} {
+ background: rgb(3, 179, 101) !important;
+ color: rgb(255, 255, 255);
+ border-color: rgb(3, 179, 101) !important;
+ box-shadow: none;
+ outline: none !important;
+ }
+ .rc-tree-select-item {
+ font-size: 16px;
+ line-height: 1.5;
+ padding: 5px 16px;
+ align-items: center;
+ cursor: pointer;
+}
+.rc-tree-select-item-option-state {
+ .bp3-control.bp3-checkbox {
+ margin-bottom: 0;
+ }
+}
+
+
+
+.rc-tree-select-tree {
+ margin: 0;
+ border: 1px solid transparent;
+}
+.rc-tree-select-tree-focused:not(.rc-tree-select-tree-active-focused) {
+ border-color: cyan;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode {
+ margin: 0;
+ padding: 0;
+ line-height: 24px;
+ white-space: nowrap;
+ list-style: none;
+ outline: 0;
+ padding: 0 5px;
+ height: 34px;
+ align-items: center;
+ display: flex !important;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode .draggable {
+ color: #333;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ -khtml-user-drag: element;
+ -webkit-user-drag: element;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-container
+ > .draggable::after {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ box-shadow: inset 0 0 0 2px red;
+ content: "";
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-container
+ ~ .rc-tree-select-tree-treenode {
+ border-left: 2px solid chocolate;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode.drop-target {
+ background-color: yellowgreen;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-target
+ ~ .rc-tree-select-tree-treenode {
+ border-left: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.filter-node
+ > .rc-tree-select-tree-node-content-wrapper {
+ color: #182026 !important;
+ font-weight: bold !important;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode ul {
+ margin: 0;
+ padding: 0 0 0 18px;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-node-content-wrapper {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ height: 34px;
+ margin: 0;
+ padding: 0;
+ text-decoration: none;
+ vertical-align: top;
+ cursor: pointer;
+ flex: 1
+}
+
+.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner:after {
+ position: absolute;
+ display: table;
+ border: 2px solid #fff;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+45deg
+) scale(1) translate(-50%,-50%);
+ opacity: 1;
+ transition: all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
+ content: " ";
+}
+.rc-tree-select-tree-checkbox-inner:after {
+ position: absolute;
+ top: 50%;
+ left: 22%;
+ display: table;
+ width: 5.71428571px;
+ height: 9.14285714px;
+ border: 2px solid #fff;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+45deg
+) scale(0) translate(-50%,-50%);
+ opacity: 0;
+ transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
+ content: " ";
+}
+
+.rc-tree-select-tree-checkbox-indeterminate .rc-tree-select-tree-checkbox-inner:after {
+ top: 50%;
+ left: 50%;
+ width: 8px;
+ height: 8px;
+ background-color: rgb(3, 179, 101) !important;
+ border: 0;
+ transform: translate(-50%,-50%) scale(1);
+ opacity: 1;
+ content: " ";
+}
+
+.rc-tree-select-tree-checkbox:hover:after, .rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox:after {
+ visibility: visible;
+}
+
+.rc-tree-select-tree-checkbox {
+ top: initial;
+ margin: 4px 8px 0 0;
+}
+.rc-tree-select-tree-checkbox {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ color: #000000d9;
+ font-size: 14px;
+ font-variant: tabular-nums;
+ line-height: 1.5715;
+ list-style: none;
+ font-feature-settings: "tnum";
+ position: relative;
+ top: 0;
+ line-height: 1;
+ white-space: nowrap;
+ outline: none;
+ cursor: pointer;
+ margin-left: 3px;
+}
+
+
+.rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox-input:focus+.rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+}
+.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+ background: rgb(3, 179, 101) !important;
+}
+
+.rc-tree-select-tree-checkbox-inner {
+ position: relative;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ direction: ltr;
+ background-color: #fff;
+ border: 1px solid #d9d9d9;
+ border-radius: 0px;
+ border-collapse: separate;
+ transition: all .3s;
+}
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree.select-tree-checkbox-checked {
+ .rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+ background: rgb(3, 179, 101) !important;
+ }
+ }
+ .single-tree-select-dropdown
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ width: 20px;
+ }
+
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ display: inline-block;
+ width: 0px;
+ height: 16px;
+ margin-right: 2px;
+ line-height: 16px;
+ vertical-align: -0.125em;
+ background-color: transparent;
+ background-image: none;
+ background-repeat: no-repeat;
+ background-attachment: scroll;
+ border: 0 none;
+ outline: none;
+ cursor: pointer;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-icon__customize,
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-checkbox.rc-tree-select-tree-icon__customize,
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle.rc-tree-select-tree-icon__customize {
+ background-image: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-icon_loading {
+ margin-right: 2px;
+ vertical-align: top;
+ background: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher-noop {
+ cursor: auto;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_open {
+ background-position: -93px -56px;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_close {
+ background-position: -75px -56px;
+}
+.rc-tree-select-tree:not(.rc-tree-select-tree-show-line)
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-switcher-noop {
+ background: none;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:not(:last-child)
+ > ul {
+ background: none;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:not(:last-child)
+ > .rc-tree-select-tree-switcher-noop {
+ background-position: -56px -18px;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:last-child
+ > .rc-tree-select-tree-switcher-noop {
+ background-position: -56px -36px;
+}
+.rc-tree-select-tree-child-tree {
+ display: none;
+}
+.rc-tree-select-tree-child-tree-open {
+ display: block;
+}
+.rc-tree-select-tree-treenode-disabled
+ > span:not(.rc-tree-select-tree-switcher),
+.rc-tree-select-tree-treenode-disabled > a,
+.rc-tree-select-tree-treenode-disabled > a span {
+ color: #767676;
+ cursor: not-allowed;
+}
+.rc-tree-select-tree-treenode-active {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-tree-treenode:hover {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-tree-node-selected {
+ background-color: none;
+ box-shadow: 0 0 0 0 #ffb951;
+ opacity: 1;
+}
+.rc-tree-select-tree-icon__open {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px -16px;
+}
+.rc-tree-select-tree-icon__close {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px 0;
+}
+.rc-tree-select-tree-icon__docu {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px -32px;
+}
+.rc-tree-select-tree-icon__customize {
+ margin-right: 2px;
+ vertical-align: top;
+}
+.rc-tree-select-tree-title {
+ display: inline-block;
+ margin-left: 10px;
+ font-size: 16px !important;
+}
+.rc-tree-select-tree-indent {
+ display: inline-block;
+ vertical-align: bottom;
+ height: 0;
+}
+.rc-tree-select-tree-indent-unit {
+ width: 25px;
+ display: inline-block;
+}
+
+ }
+`;
+
+export const TreeSelectContainer = styled.div<{
+ compactMode: boolean;
+ allowClear: boolean;
+}>`
+ display: flex;
+ flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
+ align-items: ${(props) => (props.compactMode ? "center" : "left")};
+
+ label.tree-select-label {
+ margin-bottom: ${(props) => (props.compactMode ? "0px" : "5px")};
+ margin-right: ${(props) => (props.compactMode ? "10px" : "0px")};
+ }
+ .rc-tree-select {
+ display: inline-block;
+ font-size: 12px;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ cursor: pointer;
+ flex: 1 1;
+ .rc-tree-select-selection-placeholder {
+ pointer-events: none;
+ position: absolute;
+ top: 50%;
+ right: 11px;
+ left: 11px;
+ transform: translateY(-50%);
+ transition: all 0.3s;
+ flex: 1;
+ overflow: hidden;
+ color: #bfbfbf;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ pointer-events: none;
+ font-size: 14px;
+ }
+ .rc-tree-select-selection-search-input {
+ appearance: none;
+ &::-webkit-search-cancel-button {
+ display: none;
+ appearance: none;
+ }
+ }
+ .rc-tree-select-selection-overflow-item-suffix {
+ position: relative !important;
+ left: 0px !important;
+ }
+ }
+ .rc-tree-select-disabled {
+ cursor: not-allowed;
+ input {
+ cursor: not-allowed;
+ }
+ .rc-tree-select-selector {
+ opacity: 0.3;
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-loading {
+ .rc-tree-select-arrow-icon {
+ &::after {
+ box-sizing: border-box;
+ width: 12px;
+ height: 12px;
+ border-radius: 100%;
+ border: 2px solid #999;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+ transform: none;
+ margin-top: 4px;
+ animation: rcSelectLoadingIcon 0.5s infinite;
+ }
+ }
+ }
+ .rc-tree-select-single .rc-tree-select-selector {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 1px;
+ padding-right: 20px;
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ background-color: white;
+ height: 100%;
+ .rc-tree-select-selection-search {
+ width: 100%;
+ height: 100%;
+ input {
+ width: 100%;
+ appearance: none;
+ &::-webkit-search-cancel-button {
+ display: none;
+ appearance: none;
+ }
+ font-family: system-ui;
+
+ height: 100%;
+ border: none;
+ }
+ }
+ .rc-tree-select-selection-item {
+ pointer-events: none;
+ position: absolute;
+ top: 50%;
+ right: 11px;
+ left: 11px;
+ transform: translateY(-50%);
+ transition: all 0.3s;
+ flex: 1;
+ overflow: hidden;
+ color: #231f20;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ pointer-events: none;
+ font-size: 14px;
+ }
+ }
+ .rc-tree-select-multiple {
+ .rc-tree-select-selector {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 1px;
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ background-color: white;
+ .rc-tree-select-selection-item {
+ background: none;
+ border: 1px solid rgb(208, 215, 221);
+ border-radius: 2px;
+ margin: 3px 2px;
+ max-width: 273.926px;
+ height: 24px;
+ color: #182026;
+ overflow-wrap: break-word;
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ box-shadow: none;
+ font-size: 12px;
+ line-height: 16px;
+ min-height: 20px;
+ min-width: 20px;
+ padding: 2px 6px;
+ position: relative;
+ }
+ .rc-tree-select-selection-item-disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+ .rc-tree-select-selection-overflow {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ align-content: center;
+ }
+ .rc-tree-select-selection-overflow-item {
+ flex: none;
+ max-width: 100%;
+ }
+ .rc-tree-select-selection-search {
+ position: relative;
+ max-width: 100%;
+ margin-bottom: 2px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+ .rc-tree-select-selection-search-input {
+ padding: 1px;
+ font-family: system-ui;
+ width: 5px;
+ margin: 0px;
+ display: flex;
+ height: 26px;
+ flex: 1 1 0%;
+ border: none;
+ outline: none;
+ width: 100%;
+ }
+ .rc-tree-select-selection-search-mirror {
+ padding: 1px;
+ font-family: system-ui;
+ width: 5px;
+ margin: 0px;
+ display: flex;
+ height: 26px;
+ flex: 1 1 0%;
+ position: absolute;
+ z-index: 999;
+ white-space: nowrap;
+ position: none;
+ left: 0;
+ top: 0;
+ visibility: hidden;
+ }
+ }
+ }
+ .rc-tree-select-selection-item-content {
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin-right: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ word-wrap: normal;
+ font-size: 12px;
+ line-height: 16px;
+ }
+ .rc-tree-select-allow-clear {
+ .rc-tree-select-clear {
+ position: absolute;
+ right: 20px;
+ right: 25px;
+ top: -1px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ z-index: -1;
+ .rc-tree-select-clear-icon {
+ font-size: 18px;
+ font-weight: bold;
+ }
+ }
+ }
+ .rc-tree-select-allow-clear.rc-tree-select-focused {
+ .rc-tree-select-clear {
+ z-index: 1;
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-multiple {
+ .rc-tree-select-selector {
+ padding-right: ${({ allowClear }) => (allowClear ? "40px" : "20px")};
+
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ height: inherit;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ }
+ }
+ .rc-tree-select-show-arrow {
+ .rc-tree-select-arrow {
+ pointer-events: none;
+ position: absolute;
+ right: 5px;
+ top: 0;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+ .rc-tree-select-arrow-icon {
+ &::after {
+ content: "";
+ border: 5px solid transparent;
+ width: 0;
+ height: 0;
+ display: inline-block;
+ border-top-color: #999;
+ transform: translateY(5px);
+ }
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-focused {
+ .rc-tree-select-selector {
+ border: 1px solid rgb(128, 189, 255);
+ outline: 0px;
+ box-shadow: rgba(0, 123, 255, 0.25) 0px 0px 0px 0.1rem;
+ }
+ }
+`;
+export const StyledCheckbox = styled(Checkbox)`
+ &&.${Classes.CHECKBOX}.${Classes.CONTROL} {
+ margin: 0;
+ }
+`;
+
+export const inputIcon = (): JSX.Element => (
+
+);
diff --git a/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx
new file mode 100644
index 0000000000..7b1316ddf8
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx
@@ -0,0 +1,191 @@
+import React, {
+ ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import TreeSelect, { TreeSelectProps as SelectProps } from "rc-tree-select";
+import {
+ TreeSelectContainer,
+ DropdownStyles,
+ inputIcon,
+ StyledLabel,
+ TextLabelWrapper,
+} from "./index.styled";
+import "rc-tree-select/assets/index.less";
+import { DefaultValueType } from "rc-tree-select/lib/interface";
+import { TreeNodeProps } from "rc-tree-select/lib/TreeNode";
+import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil";
+import {
+ CANVAS_CLASSNAME,
+ MODAL_PORTAL_CLASSNAME,
+ TextSize,
+} from "constants/WidgetConstants";
+import { Classes } from "@blueprintjs/core";
+export interface TreeSelectProps
+ extends Required<
+ Pick<
+ SelectProps,
+ | "disabled"
+ | "options"
+ | "placeholder"
+ | "loading"
+ | "dropdownStyle"
+ | "allowClear"
+ >
+ > {
+ value?: DefaultValueType;
+ onChange: (value?: DefaultValueType, labelList?: ReactNode[]) => void;
+ expandAll: boolean;
+ mode: CheckedStrategy;
+ labelText?: string;
+ labelTextColor?: string;
+ labelTextSize?: TextSize;
+ labelStyle?: string;
+ compactMode: boolean;
+}
+
+const getSvg = (style = {}) => (
+
+
+
+);
+
+const switcherIcon = (treeNode: TreeNodeProps) => {
+ if (treeNode.isLeaf) {
+ return (
+
+ );
+ }
+ return getSvg({ transform: `rotate(${treeNode.expanded ? 90 : 0}deg)` });
+};
+
+function MultiTreeSelectComponent({
+ allowClear,
+ compactMode,
+ disabled,
+ dropdownStyle,
+ expandAll,
+ labelStyle,
+ labelText,
+ labelTextColor,
+ labelTextSize,
+ loading,
+ mode,
+ onChange,
+ options,
+ placeholder,
+ value,
+}: TreeSelectProps): JSX.Element {
+ const [key, setKey] = useState(Math.random());
+ const _menu = useRef(null);
+
+ // treeDefaultExpandAll is uncontrolled after first render,
+ // using this to force render to respond to changes in expandAll
+ useEffect(() => {
+ setKey(Math.random());
+ }, [expandAll]);
+
+ const getDropdownPosition = useCallback(() => {
+ const node = _menu.current;
+ if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
+ return document.querySelector(
+ `.${MODAL_PORTAL_CLASSNAME}`,
+ ) as HTMLElement;
+ }
+ return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
+ }, []);
+
+ const onClear = useCallback(() => onChange([], []), []);
+
+ return (
+ }
+ >
+
+ {labelText && (
+
+
+ {labelText}
+
+
+ )}
+ `+${e.length} more`}
+ multiple
+ notFoundContent="No item Found"
+ onChange={onChange}
+ onClear={onClear}
+ placeholder={placeholder}
+ showArrow
+ showCheckedStrategy={mode}
+ showSearch
+ style={{ width: "100%" }}
+ switcherIcon={switcherIcon}
+ transitionName="rc-tree-select-dropdown-slide-up"
+ treeCheckable={
+
+ }
+ treeData={options}
+ treeDefaultExpandAll={expandAll}
+ treeIcon
+ treeNodeFilterProp="label"
+ value={value}
+ />
+
+ );
+}
+
+export default MultiTreeSelectComponent;
diff --git a/app/client/src/widgets/MultiSelectTreeWidget/icon.svg b/app/client/src/widgets/MultiSelectTreeWidget/icon.svg
new file mode 100644
index 0000000000..181e4e55cc
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectTreeWidget/icon.svg
@@ -0,0 +1 @@
+
diff --git a/app/client/src/widgets/MultiSelectTreeWidget/index.ts b/app/client/src/widgets/MultiSelectTreeWidget/index.ts
new file mode 100644
index 0000000000..0b16de9246
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectTreeWidget/index.ts
@@ -0,0 +1,51 @@
+import Widget from "./widget";
+import IconSVG from "./icon.svg";
+import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+
+export const CONFIG = {
+ type: Widget.getWidgetType(),
+ name: "Multi Select Tree",
+ iconSVG: IconSVG,
+ needsMeta: true,
+ defaults: {
+ rows: 1.72 * GRID_DENSITY_MIGRATION_V1,
+ columns: 4 * GRID_DENSITY_MIGRATION_V1,
+ mode: "SHOW_ALL",
+ options: [
+ {
+ label: "Blue",
+ value: "BLUE",
+ children: [
+ {
+ label: "Dark Blue",
+ value: "DARK BLUE",
+ },
+ {
+ label: "Light Blue",
+ value: "LIGHT BLUE",
+ },
+ ],
+ },
+ { label: "Green", value: "GREEN" },
+ { label: "Red", value: "RED" },
+ ],
+ widgetName: "MultiSelectTree",
+ defaultOptionValue: ["GREEN"],
+ version: 1,
+ isVisible: true,
+ isRequired: false,
+ isDisabled: false,
+ allowClear: false,
+ expandAll: false,
+ placeholderText: "select option(s)",
+ labelText: "Label",
+ },
+ properties: {
+ derived: Widget.getDerivedPropertiesMap(),
+ default: Widget.getDefaultPropertiesMap(),
+ meta: Widget.getMetaPropertiesMap(),
+ config: Widget.getPropertyPaneConfig(),
+ },
+};
+
+export default Widget;
diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx
new file mode 100644
index 0000000000..2cc9b667f5
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx
@@ -0,0 +1,465 @@
+import React, { ReactNode } from "react";
+import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
+import { TextSize, WidgetType } from "constants/WidgetConstants";
+import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
+import { isArray, findIndex } from "lodash";
+import {
+ ValidationResponse,
+ ValidationTypes,
+} from "constants/WidgetValidation";
+import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
+import { DefaultValueType } from "rc-select/lib/interface/generator";
+import { Layers } from "constants/Layers";
+import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil";
+import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import { AutocompleteDataType } from "utils/autocomplete/TernServer";
+import MultiTreeSelectComponent from "../component";
+
+function defaultOptionValueValidation(value: unknown): ValidationResponse {
+ let values: string[] = [];
+ if (typeof value === "string") {
+ try {
+ values = JSON.parse(value);
+ if (!Array.isArray(values)) {
+ throw new Error();
+ }
+ } catch {
+ values = value.length ? value.split(",") : [];
+ if (values.length > 0) {
+ values = values.map((_v: string) => _v.trim());
+ }
+ }
+ }
+ if (Array.isArray(value)) {
+ values = Array.from(new Set(value));
+ }
+
+ return {
+ isValid: true,
+ parsed: values,
+ };
+}
+class MultiSelectTreeWidget extends BaseWidget<
+ MultiSelectTreeWidgetProps,
+ WidgetState
+> {
+ static getPropertyPaneConfig() {
+ return [
+ {
+ sectionName: "General",
+ children: [
+ {
+ helpText: "Mode to Display options",
+ propertyName: "mode",
+ label: "Mode",
+ controlType: "DROP_DOWN",
+ options: [
+ {
+ label: "Display only parent items",
+ value: "SHOW_PARENT",
+ },
+ {
+ label: "Display only child items",
+ value: "SHOW_CHILD",
+ },
+ {
+ label: "Display all items",
+ value: "SHOW_ALL",
+ },
+ ],
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ {
+ helpText:
+ "Allows users to select multiple options. Values must be unique",
+ propertyName: "options",
+ label: "Options",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter option value",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ isJSConvertible: false,
+ validation: {
+ type: ValidationTypes.NESTED_OBJECT_ARRAY,
+ params: {
+ unique: ["value"],
+ default: [],
+ children: {
+ type: ValidationTypes.OBJECT,
+ params: {
+ allowedKeys: [
+ {
+ name: "label",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "value",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "children",
+ type: ValidationTypes.ARRAY,
+ required: false,
+ params: {
+ children: {
+ type: ValidationTypes.OBJECT,
+ params: {
+ allowedKeys: [
+ {
+ name: "label",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "value",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ evaluationSubstitutionType:
+ EvaluationSubstitutionType.SMART_SUBSTITUTE,
+ },
+ {
+ helpText: "Selects the option with value by default",
+ propertyName: "defaultOptionValue",
+ label: "Default Value",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter option value",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.FUNCTION,
+ params: {
+ fn: defaultOptionValueValidation,
+ expected: {
+ type: "Array of values",
+ example: `['value1', 'value2']`,
+ autocompleteDataType: AutocompleteDataType.ARRAY,
+ },
+ },
+ },
+ },
+ {
+ helpText: "Label Text",
+ propertyName: "labelText",
+ label: "Label Text",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter Label text",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ helpText: "Input Place Holder",
+ propertyName: "placeholderText",
+ label: "Placeholder",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter placeholder text",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ helpText: "Controls the visibility of the widget",
+ propertyName: "isVisible",
+ label: "Visible",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "isDisabled",
+ label: "Disabled",
+ helpText: "Disables input to this widget",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "isRequired",
+ label: "Required",
+ helpText: "Makes input to the widget mandatory",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "allowClear",
+ label: "Clear all Selections",
+ helpText: "Enables Icon to clear all Selections",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "expandAll",
+ label: "Expand all by default",
+ helpText: "Expand All nested options",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ ],
+ },
+ {
+ sectionName: "Styles",
+ children: [
+ {
+ propertyName: "labelTextColor",
+ label: "Label Text Color",
+ controlType: "COLOR_PICKER",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ propertyName: "labelTextSize",
+ label: "Label Text Size",
+ controlType: "DROP_DOWN",
+ options: [
+ {
+ label: "Heading 1",
+ value: "HEADING1",
+ subText: "24px",
+ icon: "HEADING_ONE",
+ },
+ {
+ label: "Heading 2",
+ value: "HEADING2",
+ subText: "18px",
+ icon: "HEADING_TWO",
+ },
+ {
+ label: "Heading 3",
+ value: "HEADING3",
+ subText: "16px",
+ icon: "HEADING_THREE",
+ },
+ {
+ label: "Paragraph",
+ value: "PARAGRAPH",
+ subText: "14px",
+ icon: "PARAGRAPH",
+ },
+ {
+ label: "Paragraph 2",
+ value: "PARAGRAPH2",
+ subText: "12px",
+ icon: "PARAGRAPH_TWO",
+ },
+ ],
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ {
+ propertyName: "labelStyle",
+ label: "Label Font Style",
+ controlType: "BUTTON_TABS",
+ options: [
+ {
+ icon: "BOLD_FONT",
+ value: "BOLD",
+ },
+ {
+ icon: "ITALICS_FONT",
+ value: "ITALIC",
+ },
+ ],
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ ],
+ },
+ {
+ sectionName: "Actions",
+ children: [
+ {
+ helpText: "Triggers an action when a user selects an option",
+ propertyName: "onOptionChange",
+ label: "onOptionChange",
+ controlType: "ACTION_SELECTOR",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: true,
+ },
+ ],
+ },
+ ];
+ }
+
+ static getDerivedPropertiesMap() {
+ return {
+ selectedOptionLabels: `{{ this.selectedLabel }}`,
+ selectedOptionValues:
+ '{{ this.selectedOptionValueArr.filter((o) => JSON.stringify(this.options).match(new RegExp(`"value":"${o}"`, "g")) )}}',
+ isValid: `{{ this.isRequired ? this.selectedOptionValues?.length > 0 : true}}`,
+ };
+ }
+
+ static getDefaultPropertiesMap(): Record {
+ return {
+ selectedOptionValueArr: "defaultOptionValue",
+ selectedLabel: "defaultOptionValue",
+ };
+ }
+
+ static getMetaPropertiesMap(): Record {
+ return {
+ selectedOptionValueArr: undefined,
+ selectedLabel: [],
+ };
+ }
+ getPageView() {
+ const options =
+ isArray(this.props.options) &&
+ !this.props.__evaluation__?.errors.options.length
+ ? this.props.options
+ : [];
+
+ const values = isArray(this.props.selectedOptionValueArr)
+ ? this.props.selectedOptionValueArr
+ : [];
+
+ const filteredValue = this.filterValues(values);
+
+ return (
+
+ 1
+ )
+ }
+ disabled={this.props.isDisabled ?? false}
+ dropdownStyle={{
+ zIndex: Layers.dropdownModalWidget,
+ }}
+ expandAll={this.props.expandAll}
+ labelStyle={this.props.labelStyle}
+ labelText={this.props.labelText}
+ labelTextColor={this.props.labelTextColor}
+ labelTextSize={this.props.labelTextSize}
+ loading={this.props.isLoading}
+ mode={this.props.mode}
+ onChange={this.onOptionChange}
+ options={options}
+ placeholder={this.props.placeholderText as string}
+ value={filteredValue}
+ />
+ );
+ }
+
+ onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
+ this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
+ triggerPropertyName: "onOptionChange",
+ dynamicString: this.props.onOptionChange,
+ event: {
+ type: EventType.ON_OPTION_CHANGE,
+ },
+ });
+
+ this.props.updateWidgetMetaProperty("selectedOptionValueArr", value, {
+ triggerPropertyName: "onOptionChange",
+ dynamicString: this.props.onOptionChange,
+ event: {
+ type: EventType.ON_OPTION_CHANGE,
+ },
+ });
+ };
+
+ flat(array: DropdownOption[]) {
+ let result: { value: string }[] = [];
+ array.forEach((a) => {
+ result.push({ value: a.value });
+ if (Array.isArray(a.children)) {
+ result = result.concat(this.flat(a.children));
+ }
+ });
+ return result;
+ }
+
+ filterValues(values: string[] | undefined) {
+ const options = this.props.options
+ ? this.flat(this.props.options as DropdownOption[])
+ : [];
+ if (isArray(values)) {
+ return values.filter((o) => {
+ const index = findIndex(options, { value: o });
+ return index > -1;
+ });
+ }
+ }
+
+ static getWidgetType(): WidgetType {
+ return "MULTI_SELECT_TREE_WIDGET";
+ }
+}
+
+export interface DropdownOption {
+ label: string;
+ value: string;
+ disabled?: boolean;
+ children?: DropdownOption[];
+}
+
+export interface MultiSelectTreeWidgetProps extends WidgetProps {
+ placeholderText?: string;
+ selectedIndexArr?: number[];
+ options?: DropdownOption[];
+ onOptionChange: string;
+ defaultOptionValue: string[];
+ isRequired: boolean;
+ isLoading: boolean;
+ allowClear: boolean;
+ labelText?: string;
+ selectedLabel: string[];
+ selectedOptionValueArr: string[];
+ selectedOptionValues: string[];
+ selectedOptionLabels: string[];
+ expandAll: boolean;
+ mode: CheckedStrategy;
+ labelTextColor?: string;
+ labelTextSize?: TextSize;
+ labelStyle?: string;
+}
+
+export default MultiSelectTreeWidget;
diff --git a/app/client/src/widgets/MultiSelectWidget/component/index.tsx b/app/client/src/widgets/MultiSelectWidget/component/index.tsx
index ed80edbd25..aa4bd94cc2 100644
--- a/app/client/src/widgets/MultiSelectWidget/component/index.tsx
+++ b/app/client/src/widgets/MultiSelectWidget/component/index.tsx
@@ -48,13 +48,13 @@ function MultiSelectComponent({
const [isSelectAll, setIsSelectAll] = useState(false);
const _menu = useRef(null);
- const getDropdownPosition = useCallback((node: HTMLElement | null) => {
+ const getDropdownPosition = useCallback(() => {
+ const node = _menu.current;
if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
return document.querySelector(
`.${MODAL_PORTAL_CLASSNAME}`,
) as HTMLElement;
}
- // TODO: Use generateClassName func.
return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
}, []);
@@ -129,7 +129,7 @@ function MultiSelectComponent({
dropdownRender={dropdownRender}
dropdownStyle={dropdownStyle}
filterOption={serverSideFiltering ? false : filterOption}
- getPopupContainer={() => getDropdownPosition(_menu.current)}
+ getPopupContainer={getDropdownPosition}
inputIcon={inputIcon}
loading={loading}
maxTagCount={"responsive"}
diff --git a/app/client/src/widgets/SingleSelectTreeWidget/component/index.styled.tsx b/app/client/src/widgets/SingleSelectTreeWidget/component/index.styled.tsx
new file mode 100644
index 0000000000..35a35478ca
--- /dev/null
+++ b/app/client/src/widgets/SingleSelectTreeWidget/component/index.styled.tsx
@@ -0,0 +1,888 @@
+import React from "react";
+import { Checkbox, Classes, Label } from "@blueprintjs/core";
+import styled, { keyframes } from "styled-components";
+import { Colors } from "constants/Colors";
+import { createGlobalStyle } from "constants/DefaultTheme";
+import {
+ FontStyleTypes,
+ TextSize,
+ TEXT_SIZES,
+} from "constants/WidgetConstants";
+
+export const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
+ return ;
+};
+
+export const TextLabelWrapper = styled.div<{
+ compactMode: boolean;
+}>`
+ ${(props) =>
+ props.compactMode ? "&&& {margin-right: 5px;}" : "width: 100%;"}
+ display: flex;
+`;
+
+export const StyledLabel = styled(Label)<{
+ $compactMode: boolean;
+ $labelText?: string;
+ $labelTextColor?: string;
+ $labelTextSize?: TextSize;
+ $labelStyle?: string;
+}>`
+ overflow-y: hidden;
+ text-overflow: ellipsis;
+ width: ${(props) => (props.$compactMode ? "auto" : "100%")};
+ text-align: left;
+ color: ${(props) => props.$labelTextColor || "inherit"};
+ font-size: ${(props) =>
+ props.$labelTextSize ? TEXT_SIZES[props.$labelTextSize] : "14px"};
+ font-weight: ${(props) =>
+ props?.$labelStyle?.includes(FontStyleTypes.BOLD) ? "bold" : "normal"};
+ font-style: ${(props) =>
+ props?.$labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : ""};
+`;
+
+const rcSelectDropdownSlideUpIn = keyframes`
+ 0% {
+ opacity: 0;
+ transform-origin: 0% 0%;
+ }
+ 100% {
+ opacity: 1;
+ transform-origin: 0% 0%;
+ }
+`;
+
+const rcSelectDropdownSlideUpOut = keyframes`
+ 0% {
+ opacity: 1;
+ transform-origin: 0% 0%;
+ }
+100% {
+ opacity: 0;
+ transform-origin: 0% 0%;
+ }
+`;
+
+export const DropdownStyles = createGlobalStyle`
+.rc-tree-select-dropdown-hidden {
+ display: none;
+}
+.rc-tree-select-item-group {
+ color: #999;
+ font-weight: bold;
+ font-size: 80%;
+}
+.rc-tree-select-item-option {
+ position: relative;
+ display: flex;
+
+ flex-direction: row-reverse;
+ .rc-tree-select-item-option-state {
+ pointer-events: all;
+ margin-right: 10px;
+ }
+}
+.rc-tree-select-item-option-grouped {
+ padding-left: 24px;
+}
+.rc-tree-select-item-option-content {
+ flex: 1 1 0;
+}
+.rc-tree-select-item-option-active {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-item-option-selected {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-item-option-disabled {
+ color: #999;
+}
+.rc-tree-select-item-empty {
+ text-align: center;
+ color: #999;
+}
+.rc-tree-select-selection__choice-zoom {
+ transition: all 0s;
+}
+.rc-tree-select-selection__choice-zoom-appear {
+ opacity: 0;
+}
+.rc-tree-select-selection__choice-zoom-appear.rc-tree-select-selection__choice-zoom-appear-active {
+ opacity: 1;
+}
+.rc-tree-select-selection__choice-zoom-leave {
+ opacity: 1;
+}
+.rc-tree-select-selection__choice-zoom-leave.rc-tree-select-selection__choice-zoom-leave-active {
+ opacity: 0;
+}
+.rc-tree-select-dropdown-slide-up-enter {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-appear {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-leave {
+ animation-duration: 0s;
+ animation-fill-mode: both;
+ transform-origin: 0 0;
+ opacity: 1;
+ animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
+ animation-play-state: paused;
+}
+.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name:${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-bottomLeft {
+ animation-name: ${rcSelectDropdownSlideUpOut};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-enter.rc-tree-select-dropdown-slide-up-enter-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-appear.rc-tree-select-dropdown-slide-up-appear-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpIn};
+ animation-play-state: running;
+}
+.rc-tree-select-dropdown-slide-up-leave.rc-tree-select-dropdown-slide-up-leave-active.rc-tree-select-dropdown-placement-topLeft {
+ animation-name: ${rcSelectDropdownSlideUpOut};
+ animation-play-state: running;
+}
+
+
+
+
+.tree-select-dropdown.single-tree-select-dropdown {
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode.rc-tree-select-tree-treenode-disabled
+ span.rc-tree-select-tree-iconEle {
+cursor: not-allowed;
+ }
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ position: relative;
+ cursor: pointer;
+ margin-left: 5px;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ direction: ltr;
+ background-color: #fff;
+border: 1px solid #E8E8E8;
+ border-radius: 100%;
+ border-collapse: separate;
+ transition: all .3s;
+ :after{
+ position: absolute;
+ top: 50%;
+ left: 52%;
+ display: table;
+ width: 10px;
+ height: 10px;
+ border: none;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+ 45deg
+ ) scale(0) translate(-50%,-50%);
+ opacity: 0;
+ transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
+ content: " ";
+ }
+
+ }
+
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-node-selected
+ span.rc-tree-select-tree-iconEle {
+ :after{
+ width: 10px;
+ height: 10px;
+ transform: translate(-50%,-50%) scale(1);
+ background: rgb(3, 179, 101) !important;
+ opacity: 1;
+ content: " ";
+ border-radius: 100%;
+
+ }
+ }
+
+}
+.tree-select-dropdown {
+ min-height: 100px;
+ min-width: 250px !important;
+ position: absolute;
+ background: #fff;
+ width: 100%;
+ border-radius: 0px;
+ margin-top: 10px;
+ padding: 12px;
+ background: white;
+ box-shadow: 0 0 2px rgb(0 0 0 / 20%) !important;
+ &&&& .${Classes.ALIGN_LEFT} {
+ font-size: 16px;
+ padding-bottom: 10px;
+ margin-left: 16px ;
+ .${Classes.CONTROL_INDICATOR} {
+ margin-right: 20px;
+ }
+ }
+ &&&& .${Classes.CONTROL} .${Classes.CONTROL_INDICATOR} {
+ background: white;
+ box-shadow: none;
+ border-width: 2px;
+ border-style: solid;
+ border-color: ${Colors.GEYSER};
+ &::before {
+ width: auto;
+ height: 1em;
+ }
+ }
+ .${Classes.CONTROL} input:checked ~ .${Classes.CONTROL_INDICATOR} {
+ background: rgb(3, 179, 101) !important;
+ color: rgb(255, 255, 255);
+ border-color: rgb(3, 179, 101) !important;
+ box-shadow: none;
+ outline: none !important;
+ }
+ .rc-tree-select-item {
+ font-size: 16px;
+ line-height: 1.5;
+ padding: 5px 16px;
+ align-items: center;
+ cursor: pointer;
+}
+.rc-tree-select-item-option-state {
+ .bp3-control.bp3-checkbox {
+ margin-bottom: 0;
+ }
+}
+
+
+
+.rc-tree-select-tree {
+ margin: 0;
+ border: 1px solid transparent;
+}
+.rc-tree-select-tree-focused:not(.rc-tree-select-tree-active-focused) {
+ border-color: cyan;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode {
+ margin: 0;
+ padding: 0;
+ line-height: 24px;
+ white-space: nowrap;
+ list-style: none;
+ outline: 0;
+ padding: 0 5px;
+ height: 34px;
+ align-items: center;
+ display: flex !important;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode .draggable {
+ color: #333;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ -khtml-user-drag: element;
+ -webkit-user-drag: element;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-container
+ > .draggable::after {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ box-shadow: inset 0 0 0 2px red;
+ content: "";
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-container
+ ~ .rc-tree-select-tree-treenode {
+ border-left: 2px solid chocolate;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode.drop-target {
+ background-color: yellowgreen;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.drop-target
+ ~ .rc-tree-select-tree-treenode {
+ border-left: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode.filter-node
+ > .rc-tree-select-tree-node-content-wrapper {
+ color: #182026 !important;
+ font-weight: bold !important;
+}
+.rc-tree-select-tree .rc-tree-select-tree-treenode ul {
+ margin: 0;
+ padding: 0 0 0 18px;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-node-content-wrapper {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ height: 34px;
+ margin: 0;
+ padding: 0;
+ text-decoration: none;
+ vertical-align: top;
+ cursor: pointer;
+ flex: 1
+}
+
+.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner:after {
+ position: absolute;
+ display: table;
+ border: 2px solid #fff;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+45deg
+) scale(1) translate(-50%,-50%);
+ opacity: 1;
+ transition: all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
+ content: " ";
+}
+.rc-tree-select-tree-checkbox-inner:after {
+ position: absolute;
+ top: 50%;
+ left: 22%;
+ display: table;
+ width: 5.71428571px;
+ height: 9.14285714px;
+ border: 2px solid #fff;
+ border-top: 0;
+ border-left: 0;
+ transform: rotate(
+45deg
+) scale(0) translate(-50%,-50%);
+ opacity: 0;
+ transition: all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;
+ content: " ";
+}
+
+.rc-tree-select-tree-checkbox-indeterminate .rc-tree-select-tree-checkbox-inner:after {
+ top: 50%;
+ left: 50%;
+ width: 8px;
+ height: 8px;
+ background-color: rgb(3, 179, 101) !important;
+ border: 0;
+ transform: translate(-50%,-50%) scale(1);
+ opacity: 1;
+ content: " ";
+}
+
+.rc-tree-select-tree-checkbox:hover:after, .rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox:after {
+ visibility: visible;
+}
+
+.rc-tree-select-tree-checkbox {
+ top: initial;
+ margin: 4px 8px 0 0;
+}
+.rc-tree-select-tree-checkbox {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ color: #000000d9;
+ font-size: 14px;
+ font-variant: tabular-nums;
+ line-height: 1.5715;
+ list-style: none;
+ font-feature-settings: "tnum";
+ position: relative;
+ top: 0;
+ line-height: 1;
+ white-space: nowrap;
+ outline: none;
+ cursor: pointer;
+ margin-left: 3px;
+}
+
+
+.rc-tree-select-tree-checkbox-wrapper:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox:hover .rc-tree-select-tree-checkbox-inner, .rc-tree-select-tree-checkbox-input:focus+.rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+}
+.rc-tree-select-tree-checkbox-checked .rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+ background: rgb(3, 179, 101) !important;
+}
+
+.rc-tree-select-tree-checkbox-inner {
+ position: relative;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ direction: ltr;
+ background-color: #fff;
+ border: 1px solid #d9d9d9;
+ border-radius: 0px;
+ border-collapse: separate;
+ transition: all .3s;
+}
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree.select-tree-checkbox-checked {
+ .rc-tree-select-tree-checkbox-inner {
+ border-color: rgb(3, 179, 101) !important;
+ background: rgb(3, 179, 101) !important;
+ }
+ }
+ .single-tree-select-dropdown
+ .rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ width: 20px;
+ }
+
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle {
+ display: inline-block;
+ width: 0px;
+ height: 16px;
+ margin-right: 2px;
+ line-height: 16px;
+ vertical-align: -0.125em;
+ background-color: transparent;
+ background-image: none;
+ background-repeat: no-repeat;
+ background-attachment: scroll;
+ border: 0 none;
+ outline: none;
+ cursor: pointer;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-icon__customize,
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-checkbox.rc-tree-select-tree-icon__customize,
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-iconEle.rc-tree-select-tree-icon__customize {
+ background-image: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-icon_loading {
+ margin-right: 2px;
+ vertical-align: top;
+ background: none;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher-noop {
+ cursor: auto;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_open {
+ background-position: -93px -56px;
+}
+.rc-tree-select-tree
+ .rc-tree-select-tree-treenode
+ span.rc-tree-select-tree-switcher.rc-tree-select-tree-switcher_close {
+ background-position: -75px -56px;
+}
+.rc-tree-select-tree:not(.rc-tree-select-tree-show-line)
+ .rc-tree-select-tree-treenode
+ .rc-tree-select-tree-switcher-noop {
+ background: none;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:not(:last-child)
+ > ul {
+ background: none;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:not(:last-child)
+ > .rc-tree-select-tree-switcher-noop {
+ background-position: -56px -18px;
+}
+.rc-tree-select-tree.rc-tree-select-tree-show-line
+ .rc-tree-select-tree-treenode:last-child
+ > .rc-tree-select-tree-switcher-noop {
+ background-position: -56px -36px;
+}
+.rc-tree-select-tree-child-tree {
+ display: none;
+}
+.rc-tree-select-tree-child-tree-open {
+ display: block;
+}
+.rc-tree-select-tree-treenode-disabled
+ > span:not(.rc-tree-select-tree-switcher),
+.rc-tree-select-tree-treenode-disabled > a,
+.rc-tree-select-tree-treenode-disabled > a span {
+ color: #767676;
+ cursor: not-allowed;
+}
+.rc-tree-select-tree-treenode-active {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-tree-treenode:hover {
+ background: rgb(233, 250, 243);
+}
+.rc-tree-select-tree-node-selected {
+ background-color: none;
+ box-shadow: 0 0 0 0 #ffb951;
+ opacity: 1;
+}
+.rc-tree-select-tree-icon__open {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px -16px;
+}
+.rc-tree-select-tree-icon__close {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px 0;
+}
+.rc-tree-select-tree-icon__docu {
+ margin-right: 2px;
+ vertical-align: top;
+ background-position: -110px -32px;
+}
+.rc-tree-select-tree-icon__customize {
+ margin-right: 2px;
+ vertical-align: top;
+}
+.rc-tree-select-tree-title {
+ display: inline-block;
+ margin-left: 10px;
+ font-size: 16px !important;
+}
+.rc-tree-select-tree-indent {
+ display: inline-block;
+ vertical-align: bottom;
+ height: 0;
+}
+.rc-tree-select-tree-indent-unit {
+ width: 25px;
+ display: inline-block;
+}
+
+ }
+`;
+
+export const TreeSelectContainer = styled.div<{ compactMode: boolean }>`
+ display: flex;
+ flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
+ align-items: ${(props) => (props.compactMode ? "center" : "left")};
+
+ label.tree-select-label {
+ margin-bottom: ${(props) => (props.compactMode ? "0px" : "5px")};
+ margin-right: ${(props) => (props.compactMode ? "10px" : "0px")};
+ }
+ .rc-tree-select {
+ display: inline-block;
+ font-size: 12px;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ cursor: pointer;
+ flex: 1 1;
+ .rc-tree-select-selection-placeholder {
+ pointer-events: none;
+ position: absolute;
+ top: 50%;
+ right: 11px;
+ left: 11px;
+ transform: translateY(-50%);
+ transition: all 0.3s;
+ flex: 1;
+ overflow: hidden;
+ color: #bfbfbf;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ pointer-events: none;
+ font-size: 14px;
+ }
+ .rc-tree-select-selection-search-input {
+ appearance: none;
+ &::-webkit-search-cancel-button {
+ display: none;
+ appearance: none;
+ }
+ }
+ .rc-tree-select-selection-overflow-item-suffix {
+ position: relative !important;
+ left: 0px !important;
+ }
+ }
+ .rc-tree-select-disabled {
+ cursor: not-allowed;
+ input {
+ cursor: not-allowed;
+ }
+ .rc-tree-select-selector {
+ opacity: 0.3;
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-loading {
+ .rc-tree-select-arrow-icon {
+ &::after {
+ box-sizing: border-box;
+ width: 12px;
+ height: 12px;
+ border-radius: 100%;
+ border: 2px solid #999;
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+ transform: none;
+ margin-top: 4px;
+ animation: rcSelectLoadingIcon 0.5s infinite;
+ }
+ }
+ }
+ .rc-tree-select-single .rc-tree-select-selector {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 1px;
+ padding-right: 20px;
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ background-color: white;
+ height: 100%;
+ .rc-tree-select-selection-search {
+ width: 100%;
+ height: 100%;
+ input {
+ width: 100%;
+ appearance: none;
+ &::-webkit-search-cancel-button {
+ display: none;
+ appearance: none;
+ }
+ font-family: system-ui;
+
+ height: 100%;
+ border: none;
+ }
+ }
+ .rc-tree-select-selection-item {
+ pointer-events: none;
+ position: absolute;
+ top: 50%;
+ right: 11px;
+ left: 11px;
+ transform: translateY(-50%);
+ transition: all 0.3s;
+ flex: 1;
+ overflow: hidden;
+ color: #231f20;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ pointer-events: none;
+ font-size: 14px;
+ }
+ }
+ .rc-tree-select-multiple {
+ .rc-tree-select-selector {
+ padding-right: 20px;
+ display: flex;
+ flex-wrap: wrap;
+ padding: 1px;
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ background-color: white;
+ .rc-tree-select-selection-item {
+ background: none;
+ border: 1px solid rgb(208, 215, 221);
+ border-radius: 2px;
+ margin: 3px 2px;
+ max-width: 273.926px;
+ height: 24px;
+ color: #182026;
+ overflow-wrap: break-word;
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+ box-shadow: none;
+ font-size: 12px;
+ line-height: 16px;
+ min-height: 20px;
+ min-width: 20px;
+ padding: 2px 6px;
+ position: relative;
+ }
+ .rc-tree-select-selection-item-disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+ .rc-tree-select-selection-overflow {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ align-content: center;
+ }
+ .rc-tree-select-selection-overflow-item {
+ flex: none;
+ max-width: 100%;
+ }
+ .rc-tree-select-selection-search {
+ position: relative;
+ max-width: 100%;
+ margin-bottom: 2px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+ .rc-tree-select-selection-search-input {
+ padding: 1px;
+ font-family: system-ui;
+ width: 5px;
+ margin: 0px;
+ display: flex;
+ height: 26px;
+ flex: 1 1 0%;
+ border: none;
+ outline: none;
+ width: 100%;
+ }
+ .rc-tree-select-selection-search-mirror {
+ padding: 1px;
+ font-family: system-ui;
+ width: 5px;
+ margin: 0px;
+ display: flex;
+ height: 26px;
+ flex: 1 1 0%;
+ position: absolute;
+ z-index: 999;
+ white-space: nowrap;
+ position: none;
+ left: 0;
+ top: 0;
+ visibility: hidden;
+ }
+ }
+ }
+ .rc-tree-select-selection-item-content {
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin-right: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ word-wrap: normal;
+ font-size: 12px;
+ line-height: 16px;
+ }
+ .rc-tree-select-allow-clear {
+ .rc-tree-select-clear {
+ position: absolute;
+ right: 20px;
+ right: 25px;
+ top: -1px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ z-index: -1;
+ .rc-tree-select-clear-icon {
+ font-size: 18px;
+ font-weight: bold;
+ }
+ }
+ }
+ .rc-tree-select-allow-clear.rc-tree-select-focused {
+ .rc-tree-select-clear {
+ z-index: 1;
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-multiple {
+ .rc-tree-select-selector {
+ padding-right: 20px;
+ box-shadow: none;
+ border: 1px solid rgb(231, 231, 231);
+ border-radius: 0px;
+ height: inherit;
+ width: 100%;
+ transition: border-color 0.15s ease-in-out 0s,
+ box-shadow 0.15s ease-in-out 0s;
+ }
+ }
+ .rc-tree-select-show-arrow {
+ .rc-tree-select-arrow {
+ pointer-events: none;
+ position: absolute;
+ right: 5px;
+ top: 0;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+ .rc-tree-select-arrow-icon {
+ &::after {
+ content: "";
+ border: 5px solid transparent;
+ width: 0;
+ height: 0;
+ display: inline-block;
+ border-top-color: #999;
+ transform: translateY(5px);
+ }
+ }
+ }
+ .rc-tree-select-show-arrow.rc-tree-select-focused {
+ .rc-tree-select-selector {
+ border: 1px solid rgb(128, 189, 255);
+ outline: 0px;
+ box-shadow: rgba(0, 123, 255, 0.25) 0px 0px 0px 0.1rem;
+ }
+ }
+`;
+export const StyledCheckbox = styled(Checkbox)`
+ &&.${Classes.CHECKBOX}.${Classes.CONTROL} {
+ margin: 0;
+ }
+`;
+
+export const inputIcon = (): JSX.Element => (
+
+);
diff --git a/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx
new file mode 100644
index 0000000000..98f813f691
--- /dev/null
+++ b/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx
@@ -0,0 +1,182 @@
+import React, {
+ ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import TreeSelect, { TreeSelectProps as SelectProps } from "rc-tree-select";
+import {
+ TreeSelectContainer,
+ DropdownStyles,
+ inputIcon,
+ StyledLabel,
+ TextLabelWrapper,
+} from "./index.styled";
+import "rc-tree-select/assets/index.less";
+import { DefaultValueType } from "rc-tree-select/lib/interface";
+import { TreeNodeProps } from "rc-tree-select/lib/TreeNode";
+import {
+ CANVAS_CLASSNAME,
+ MODAL_PORTAL_CLASSNAME,
+ TextSize,
+} from "constants/WidgetConstants";
+import { Classes } from "@blueprintjs/core";
+
+export interface TreeSelectProps
+ extends Required<
+ Pick<
+ SelectProps,
+ | "disabled"
+ | "options"
+ | "placeholder"
+ | "loading"
+ | "dropdownStyle"
+ | "allowClear"
+ >
+ > {
+ value?: DefaultValueType;
+ onChange: (value?: DefaultValueType, labelList?: ReactNode[]) => void;
+ expandAll: boolean;
+ labelText?: string;
+ labelTextColor?: string;
+ labelTextSize?: TextSize;
+ labelStyle?: string;
+ compactMode: boolean;
+}
+
+const getSvg = (style = {}) => (
+
+
+
+);
+
+const switcherIcon = (treeNode: TreeNodeProps) => {
+ if (treeNode.isLeaf) {
+ return (
+
+ );
+ }
+ return getSvg({ transform: `rotate(${treeNode.expanded ? 90 : 0}deg)` });
+};
+
+function SingleSelectTreeComponent({
+ allowClear,
+ compactMode,
+ disabled,
+ dropdownStyle,
+ expandAll,
+ labelStyle,
+ labelText,
+ labelTextColor,
+ labelTextSize,
+ loading,
+ onChange,
+ options,
+ placeholder,
+ value,
+}: TreeSelectProps): JSX.Element {
+ const [key, setKey] = useState(Math.random());
+ const _menu = useRef(null);
+
+ // treeDefaultExpandAll is uncontrolled after first render,
+ // using this to force render to respond to changes in expandAll
+ useEffect(() => {
+ setKey(Math.random());
+ }, [expandAll]);
+
+ const getDropdownPosition = useCallback(() => {
+ const node = _menu.current;
+ if (Boolean(node?.closest(`.${MODAL_PORTAL_CLASSNAME}`))) {
+ return document.querySelector(
+ `.${MODAL_PORTAL_CLASSNAME}`,
+ ) as HTMLElement;
+ }
+ return document.querySelector(`.${CANVAS_CLASSNAME}`) as HTMLElement;
+ }, []);
+ const onClear = useCallback(() => onChange([], []), []);
+
+ return (
+ }
+ >
+
+ {labelText && (
+
+
+ {labelText}
+
+
+ )}
+ `+${e.length} more`}
+ notFoundContent="No item Found"
+ onChange={onChange}
+ onClear={onClear}
+ placeholder={placeholder}
+ showArrow
+ showSearch
+ style={{ width: "100%" }}
+ switcherIcon={switcherIcon}
+ transitionName="rc-tree-select-dropdown-slide-up"
+ treeData={options}
+ treeDefaultExpandAll={expandAll}
+ treeIcon
+ treeNodeFilterProp="label"
+ value={value}
+ />
+
+ );
+}
+
+export default SingleSelectTreeComponent;
diff --git a/app/client/src/widgets/SingleSelectTreeWidget/icon.svg b/app/client/src/widgets/SingleSelectTreeWidget/icon.svg
new file mode 100644
index 0000000000..dc9ccf2f42
--- /dev/null
+++ b/app/client/src/widgets/SingleSelectTreeWidget/icon.svg
@@ -0,0 +1 @@
+
diff --git a/app/client/src/widgets/SingleSelectTreeWidget/index.ts b/app/client/src/widgets/SingleSelectTreeWidget/index.ts
new file mode 100644
index 0000000000..d569f973a5
--- /dev/null
+++ b/app/client/src/widgets/SingleSelectTreeWidget/index.ts
@@ -0,0 +1,50 @@
+import Widget from "./widget";
+import IconSVG from "./icon.svg";
+import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+
+export const CONFIG = {
+ type: Widget.getWidgetType(),
+ name: "Single Select Tree",
+ iconSVG: IconSVG,
+ needsMeta: true,
+ defaults: {
+ rows: 1.7 * GRID_DENSITY_MIGRATION_V1,
+ columns: 4 * GRID_DENSITY_MIGRATION_V1,
+ options: [
+ {
+ label: "Blue",
+ value: "BLUE",
+ children: [
+ {
+ label: "Dark Blue",
+ value: "DARK BLUE",
+ },
+ {
+ label: "Light Blue",
+ value: "LIGHT BLUE",
+ },
+ ],
+ },
+ { label: "Green", value: "GREEN" },
+ { label: "Red", value: "RED" },
+ ],
+ widgetName: "SingleSelectTree",
+ defaultOptionValue: "BLUE",
+ version: 1,
+ isVisible: true,
+ isRequired: false,
+ isDisabled: false,
+ allowClear: false,
+ expandAll: false,
+ placeholderText: "select option",
+ labelText: "Label",
+ },
+ properties: {
+ derived: Widget.getDerivedPropertiesMap(),
+ default: Widget.getDefaultPropertiesMap(),
+ meta: Widget.getMetaPropertiesMap(),
+ config: Widget.getPropertyPaneConfig(),
+ },
+};
+
+export default Widget;
diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx
new file mode 100644
index 0000000000..f137cabc5b
--- /dev/null
+++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx
@@ -0,0 +1,425 @@
+import React, { ReactNode } from "react";
+import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
+import { TextSize, WidgetType } from "constants/WidgetConstants";
+import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
+import { isArray, findIndex } from "lodash";
+import {
+ ValidationResponse,
+ ValidationTypes,
+} from "constants/WidgetValidation";
+import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
+import { DefaultValueType } from "rc-select/lib/interface/generator";
+import { Layers } from "constants/Layers";
+import { isString } from "../../../utils/helpers";
+import { AutocompleteDataType } from "utils/autocomplete/TernServer";
+import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import SingleSelectTreeComponent from "../component";
+
+function defaultOptionValueValidation(value: unknown): ValidationResponse {
+ if (typeof value === "string") return { isValid: true, parsed: value.trim() };
+ if (value === undefined || value === null)
+ return {
+ isValid: false,
+ parsed: "",
+ message: "This value does not evaluate to type: string",
+ };
+ return { isValid: true, parsed: value };
+}
+class SingleSelectTreeWidget extends BaseWidget<
+ SingleSelectTreeWidgetProps,
+ WidgetState
+> {
+ static getPropertyPaneConfig() {
+ return [
+ {
+ sectionName: "General",
+ children: [
+ {
+ helpText:
+ "Allows users to select multiple options. Values must be unique",
+ propertyName: "options",
+ label: "Options",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter option value",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ isJSConvertible: false,
+ validation: {
+ type: ValidationTypes.NESTED_OBJECT_ARRAY,
+ params: {
+ unique: ["value"],
+ default: [],
+ children: {
+ type: ValidationTypes.OBJECT,
+ params: {
+ allowedKeys: [
+ {
+ name: "label",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "value",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "children",
+ type: ValidationTypes.ARRAY,
+ required: false,
+ params: {
+ children: {
+ type: ValidationTypes.OBJECT,
+ params: {
+ allowedKeys: [
+ {
+ name: "label",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ {
+ name: "value",
+ type: ValidationTypes.TEXT,
+ params: {
+ default: "",
+ required: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ evaluationSubstitutionType:
+ EvaluationSubstitutionType.SMART_SUBSTITUTE,
+ },
+ {
+ helpText: "Selects the option with value by default",
+ propertyName: "defaultOptionValue",
+ label: "Default Value",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter option value",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.FUNCTION,
+ params: {
+ fn: defaultOptionValueValidation,
+ expected: {
+ type: "value",
+ example: `value1`,
+ autocompleteDataType: AutocompleteDataType.STRING,
+ },
+ },
+ },
+ },
+ {
+ helpText: "Label Text",
+ propertyName: "labelText",
+ label: "Label Text",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter Label text",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ helpText: "Input Place Holder",
+ propertyName: "placeholderText",
+ label: "Placeholder",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Enter placeholder text",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ helpText: "Controls the visibility of the widget",
+ propertyName: "isVisible",
+ label: "Visible",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "isDisabled",
+ label: "Disabled",
+ helpText: "Disables input to this widget",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "isRequired",
+ label: "Required",
+ helpText: "Makes input to the widget mandatory",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "allowClear",
+ label: "Clear all Selections",
+ helpText: "Enables Icon to clear all Selections",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ {
+ propertyName: "expandAll",
+ label: "Expand all by default",
+ helpText: "Expand All nested options",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ ],
+ },
+ {
+ sectionName: "Styles",
+ children: [
+ {
+ propertyName: "labelTextColor",
+ label: "Label Text Color",
+ controlType: "COLOR_PICKER",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ {
+ propertyName: "labelTextSize",
+ label: "Label Text Size",
+ controlType: "DROP_DOWN",
+ options: [
+ {
+ label: "Heading 1",
+ value: "HEADING1",
+ subText: "24px",
+ icon: "HEADING_ONE",
+ },
+ {
+ label: "Heading 2",
+ value: "HEADING2",
+ subText: "18px",
+ icon: "HEADING_TWO",
+ },
+ {
+ label: "Heading 3",
+ value: "HEADING3",
+ subText: "16px",
+ icon: "HEADING_THREE",
+ },
+ {
+ label: "Paragraph",
+ value: "PARAGRAPH",
+ subText: "14px",
+ icon: "PARAGRAPH",
+ },
+ {
+ label: "Paragraph 2",
+ value: "PARAGRAPH2",
+ subText: "12px",
+ icon: "PARAGRAPH_TWO",
+ },
+ ],
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ {
+ propertyName: "labelStyle",
+ label: "Label Font Style",
+ controlType: "BUTTON_TABS",
+ options: [
+ {
+ icon: "BOLD_FONT",
+ value: "BOLD",
+ },
+ {
+ icon: "ITALICS_FONT",
+ value: "ITALIC",
+ },
+ ],
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.TEXT },
+ },
+ ],
+ },
+ {
+ sectionName: "Actions",
+ children: [
+ {
+ helpText: "Triggers an action when a user selects an option",
+ propertyName: "onOptionChange",
+ label: "onOptionChange",
+ controlType: "ACTION_SELECTOR",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: true,
+ },
+ ],
+ },
+ ];
+ }
+
+ static getDerivedPropertiesMap() {
+ return {
+ selectedOptionLabel: `{{ this.selectedLabel[0] }}`,
+ selectedOptionValue:
+ '{{ JSON.stringify(this.options).match(new RegExp(`"value":"${this.selectedOption}"`), "g") ? this.selectedOption : undefined }}',
+ isValid: `{{this.isRequired ? !!this.selectedOptionValue?.length : true}}`,
+ };
+ }
+
+ static getDefaultPropertiesMap(): Record {
+ return {
+ selectedOption: "defaultOptionValue",
+ selectedLabel: "defaultOptionValue",
+ };
+ }
+
+ static getMetaPropertiesMap(): Record {
+ return {
+ selectedOption: undefined,
+ selectedOptionValueArr: undefined,
+ selectedLabel: [],
+ };
+ }
+
+ getPageView() {
+ const options =
+ isArray(this.props.options) &&
+ !this.props.__evaluation__?.errors.options.length
+ ? this.props.options
+ : [];
+ const values: string | undefined = isString(this.props.selectedOption)
+ ? this.props.selectedOption
+ : undefined;
+
+ const filteredValue = this.filterValues(values);
+
+ return (
+
+ 1
+ )
+ }
+ disabled={this.props.isDisabled ?? false}
+ dropdownStyle={{
+ zIndex: Layers.dropdownModalWidget,
+ }}
+ expandAll={this.props.expandAll}
+ labelStyle={this.props.labelStyle}
+ labelText={this.props.labelText}
+ labelTextColor={this.props.labelTextColor}
+ labelTextSize={this.props.labelTextSize}
+ loading={this.props.isLoading}
+ onChange={this.onOptionChange}
+ options={options}
+ placeholder={this.props.placeholderText as string}
+ value={filteredValue}
+ />
+ );
+ }
+
+ onOptionChange = (value?: DefaultValueType, labelList?: ReactNode[]) => {
+ this.props.updateWidgetMetaProperty("selectedLabel", labelList, {
+ triggerPropertyName: "onOptionChange",
+ dynamicString: this.props.onOptionChange,
+ event: {
+ type: EventType.ON_OPTION_CHANGE,
+ },
+ });
+
+ this.props.updateWidgetMetaProperty("selectedOption", value, {
+ triggerPropertyName: "onOptionChange",
+ dynamicString: this.props.onOptionChange,
+ event: {
+ type: EventType.ON_OPTION_CHANGE,
+ },
+ });
+ };
+
+ flat(array: DropdownOption[]) {
+ let result: { value: string }[] = [];
+ array.forEach((a) => {
+ result.push({ value: a.value });
+ if (Array.isArray(a.children)) {
+ result = result.concat(this.flat(a.children));
+ }
+ });
+ return result;
+ }
+
+ filterValues(values: string | undefined) {
+ const options = this.props.options ? this.flat(this.props.options) : [];
+
+ if (isString(values)) {
+ const index = findIndex(options, { value: values as string });
+ return index > -1 ? values : undefined;
+ }
+ }
+
+ static getWidgetType(): WidgetType {
+ return "SINGLE_SELECT_TREE_WIDGET";
+ }
+}
+
+export interface DropdownOption {
+ label: string;
+ value: string;
+ disabled?: boolean;
+ children?: DropdownOption[];
+}
+
+export interface SingleSelectTreeWidgetProps extends WidgetProps {
+ placeholderText?: string;
+ selectedIndex?: number;
+ options?: DropdownOption[];
+ onOptionChange: string;
+ defaultOptionValue: string;
+ isRequired: boolean;
+ isLoading: boolean;
+ allowClear: boolean;
+ labelText?: string;
+ selectedLabel: string[];
+ selectedOption: string;
+ selectedOptionValue: string;
+ selectedOptionLabel: string;
+ expandAll: boolean;
+ labelTextColor?: string;
+ labelTextSize?: TextSize;
+ labelStyle?: string;
+}
+
+export default SingleSelectTreeWidget;
diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts
index 92813a3106..134043874a 100644
--- a/app/client/src/workers/validations.ts
+++ b/app/client/src/workers/validations.ts
@@ -22,6 +22,16 @@ import evaluate from "./evaluate";
import getIsSafeURL from "utils/validation/getIsSafeURL";
export const UNDEFINED_VALIDATION = "UNDEFINED_VALIDATION";
+const flat = (array: Record[], uniqueParam: string) => {
+ let result: { value: string }[] = [];
+ array.forEach((a) => {
+ result.push({ value: a[uniqueParam] });
+ if (Array.isArray(a.children)) {
+ result = result.concat(flat(a.children, uniqueParam));
+ }
+ });
+ return result;
+};
function validatePlainObject(
config: ValidationConfig,
value: Record,
@@ -242,6 +252,7 @@ export function getExpectedType(config?: ValidationConfig): string | undefined {
}
return type;
case ValidationTypes.ARRAY:
+ case ValidationTypes.NESTED_OBJECT_ARRAY:
if (config.params?.allowedValues) {
const allowed = config.params?.allowedValues.join("' | '");
return `Array<'${allowed}'>`;
@@ -626,6 +637,42 @@ export const VALIDATORS: Record = {
}
return invalidResponse;
},
+
+ [ValidationTypes.NESTED_OBJECT_ARRAY]: (
+ config: ValidationConfig,
+ value: unknown,
+ props: Record,
+ ): ValidationResponse => {
+ let response: ValidationResponse = {
+ isValid: false,
+ parsed: config.params?.default || [],
+ message: `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
+ };
+ response = VALIDATORS.ARRAY(config, value, props);
+
+ if (!response.isValid) {
+ return response;
+ }
+ // Check if all values and children values are unique
+ if (config.params?.unique && response.parsed.length) {
+ if (isArray(config.params?.unique)) {
+ for (const param of config.params?.unique) {
+ const flattenedArray = flat(response.parsed, param);
+ const shouldBeUnique = flattenedArray.map((entry) =>
+ get(entry, param, ""),
+ );
+ if (uniq(shouldBeUnique).length !== flattenedArray.length) {
+ response = {
+ ...response,
+ isValid: false,
+ message: `Array entry path:${param} must be unique. Duplicate values found`,
+ };
+ }
+ }
+ }
+ }
+ return response;
+ },
[ValidationTypes.DATE_ISO_STRING]: (
config: ValidationConfig,
value: unknown,
diff --git a/app/client/test/__mocks__/RealmExecutorMock.ts b/app/client/test/__mocks__/RealmExecutorMock.ts
index a152614261..d3469b7644 100644
--- a/app/client/test/__mocks__/RealmExecutorMock.ts
+++ b/app/client/test/__mocks__/RealmExecutorMock.ts
@@ -2,7 +2,7 @@
// Import this named export into your test file:
export const mockExecute = jest.fn().mockImplementation((src, data) => {
let finalSource = "let ";
- Object.keys(data).forEach(key => {
+ Object.keys(data).forEach((key) => {
finalSource += ` ${key} = ${JSON.stringify(data[key])}, `;
});
finalSource = finalSource.substring(0, finalSource.length - 2) + ";";
diff --git a/app/client/test/factories/Widgets/ContainerFactory.ts b/app/client/test/factories/Widgets/ContainerFactory.ts
index 8abcdc4024..c5e8f330a0 100644
--- a/app/client/test/factories/Widgets/ContainerFactory.ts
+++ b/app/client/test/factories/Widgets/ContainerFactory.ts
@@ -4,7 +4,7 @@ import { WidgetProps } from "widgets/BaseWidget";
export const ContainerFactory = Factory.Sync.makeFactory({
backgroundColor: "#FFFFFF",
- widgetName: Factory.each((i) => `Container${(i+1)}` ),
+ widgetName: Factory.each((i) => `Container${i + 1}`),
type: "CONTAINER_WIDGET",
containerStyle: "card",
isVisible: true,
diff --git a/app/client/test/factories/Widgets/RadiogroupFactory.ts b/app/client/test/factories/Widgets/RadiogroupFactory.ts
index f34c5a3335..873da00241 100644
--- a/app/client/test/factories/Widgets/RadiogroupFactory.ts
+++ b/app/client/test/factories/Widgets/RadiogroupFactory.ts
@@ -3,37 +3,39 @@ import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const RadiogroupFactory = Factory.Sync.makeFactory({
- rightColumn: 16,
- topRow: 3,
- bottomRow: 5,
- parentRowSpace: 38,
- isVisible: true,
- label: "Test Radio",
- type: "RADIO_GROUP_WIDGET",
- isLoading: false,
- defaultOptionValue: "1",
- parentColumnSpace: 34.6875,
- leftColumn: 12,
- dynamicTriggerPathList: [{
- key: "onSelectionChange"
- }],
- onSelectionChange: "{{navigateTo()}}",
- options: [
+ rightColumn: 16,
+ topRow: 3,
+ bottomRow: 5,
+ parentRowSpace: 38,
+ isVisible: true,
+ label: "Test Radio",
+ type: "RADIO_GROUP_WIDGET",
+ isLoading: false,
+ defaultOptionValue: "1",
+ parentColumnSpace: 34.6875,
+ leftColumn: 12,
+ dynamicTriggerPathList: [
{
- id: "1",
- label: "jarvis",
- value: "1"
+ key: "onSelectionChange",
+ },
+ ],
+ onSelectionChange: "{{navigateTo()}}",
+ options: [
+ {
+ id: "1",
+ label: "jarvis",
+ value: "1",
},
{
- id: "2",
- label: "marvel",
- value: "2"
+ id: "2",
+ label: "marvel",
+ value: "2",
},
{
- label: "iron",
- value: "4"
- }
- ],
+ label: "iron",
+ value: "4",
+ },
+ ],
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `RadioGroup${i + 1}`),
widgetId: generateReactKey(),
diff --git a/app/client/test/factories/Widgets/TextFactory.ts b/app/client/test/factories/Widgets/TextFactory.ts
index e0fd5e9bb1..660b8e899b 100644
--- a/app/client/test/factories/Widgets/TextFactory.ts
+++ b/app/client/test/factories/Widgets/TextFactory.ts
@@ -3,7 +3,7 @@ import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const TextFactory = Factory.Sync.makeFactory({
- widgetName: Factory.each((i) => `Text${(i+1)}`),
+ widgetName: Factory.each((i) => `Text${i + 1}`),
rightColumn: 12,
onClick: "",
isDefaultClickDisabled: true,
diff --git a/app/client/test/testMockedWidgets.tsx b/app/client/test/testMockedWidgets.tsx
index b5dee0b772..0928fac1b4 100644
--- a/app/client/test/testMockedWidgets.tsx
+++ b/app/client/test/testMockedWidgets.tsx
@@ -4,10 +4,10 @@ import React from "react";
import { useSelector } from "react-redux";
import { mockGetCanvasWidgetDsl, useMockDsl } from "./testCommon";
-export const MockCanvas = () => {
+export function MockCanvas() {
const dsl = useSelector(mockGetCanvasWidgetDsl);
- return ;
-};
+ return ;
+}
export function UpdatedMainContainer({ dsl }: any) {
useMockDsl(dsl);
return ;
diff --git a/app/client/yarn.lock b/app/client/yarn.lock
index c761ab53f6..19299c6007 100644
--- a/app/client/yarn.lock
+++ b/app/client/yarn.lock
@@ -14789,7 +14789,7 @@ rc-resize-observer@^1.0.0:
rc-util "^5.0.0"
resize-observer-polyfill "^1.5.1"
-rc-select@^12.1.10:
+rc-select@^12.0.0, rc-select@^12.1.10:
version "12.1.13"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-12.1.13.tgz#c33560ccb9339d30695b52458f55efc35af35273"
integrity sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A==
@@ -14802,6 +14802,28 @@ rc-select@^12.1.10:
rc-util "^5.9.8"
rc-virtual-list "^3.2.0"
+rc-tree-select@^4.4.0-alpha.2:
+ version "4.4.0-alpha.2"
+ resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-4.4.0-alpha.2.tgz#b96019bd401084076bedac4e49ea50321709b273"
+ integrity sha512-8PdODhXpNK13z5u3P0uWb8+ghDtpkQ+ImhxgTXZxBj1KSSi0fB1Ey/mHFLP/R3r72vmwMRGkfkpbW2G6ZRtipw==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-select "^12.0.0"
+ rc-tree "^4.0.0"
+ rc-util "^5.0.5"
+
+rc-tree@^4.0.0:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-4.2.2.tgz#4429187cbbfbecbe989714a607e3de8b3ab7763f"
+ integrity sha512-V1hkJt092VrOVjNyfj5IYbZKRMHxWihZarvA5hPL/eqm7o2+0SNkeidFYm7LVVBrAKBpOpa0l8xt04uiqOd+6w==
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+ classnames "2.x"
+ rc-motion "^2.0.1"
+ rc-util "^5.0.0"
+ rc-virtual-list "^3.0.1"
+
rc-trigger@^5.0.4:
version "5.2.9"
resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.9.tgz#795a787d2b038347dcde27b89a4a5cec8fc40f3e"
@@ -14813,7 +14835,7 @@ rc-trigger@^5.0.4:
rc-motion "^2.0.0"
rc-util "^5.5.0"
-rc-util@^5.0.0, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.9.8:
+rc-util@^5.0.0, rc-util@^5.0.5, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.9.8:
version "5.13.2"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.13.2.tgz#a8a0bb77743351841ba8bed6393e03b8d2f685c8"
integrity sha512-eYc71XXGlp96RMzg01Mhq/T3BL6OOVTDSS0urFEuvpi+e7slhJRhaHGCKy2hqJm18m9ff7VoRoptplKu60dYog==
@@ -14822,7 +14844,7 @@ rc-util@^5.0.0, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0,
react-is "^16.12.0"
shallowequal "^1.1.0"
-rc-virtual-list@^3.2.0:
+rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.3.0.tgz#2f95a6ddbbf63d78b28662b57f1e69f7472762fe"
integrity sha512-lVXpGWC6yMdwV2SHo6kc63WlqjCnb3eO72V726KA2/wh9KA6wi/swcdR3zAowuA8hJxG/lRANmY5kpLZ+Pz3iQ==