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: "Sets a Label Text", propertyName: "labelText", label: "Label Text", controlType: "INPUT_TEXT", placeholderText: "Enter Label text", isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, }, { helpText: "Sets a Placeholder Text", propertyName: "placeholderText", label: "Placeholder", controlType: "INPUT_TEXT", placeholderText: "Enter placeholder text", isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, }, { propertyName: "isRequired", label: "Required", helpText: "Makes input to the widget mandatory", controlType: "SWITCH", isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.BOOLEAN }, }, { 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: "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", defaultValue: "PARAGRAPH", 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} isValid={this.props.isValid} 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;