From ad9e5256b444c91ef6b2288bee0fdd440442c266 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 29 Oct 2024 15:02:09 +0530 Subject: [PATCH 01/92] fix editing version opening when app is launched --- frontend/src/AppBuilder/_hooks/useAppData.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 26d847ae14..0a01262e09 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -138,7 +138,8 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId } return; } const queryParams = getPreviewQueryParams(); - const isPublicAccess = currentSession?.load_app && currentSession?.authentication_failed; + const isPublicAccess = + (currentSession?.load_app && currentSession?.authentication_failed) || (!queryParams.version && mode !== 'edit'); const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess; let appDataPromise; if (isPublicAccess) { From 7791e850b04c786ee892959d3695abcc97dc05a1 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 00:40:05 +0530 Subject: [PATCH 02/92] Fixed issue causing dropdown & multiselect to not work properly --- .../AppBuilder/Widgets/Table/Components/TableRow.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Components/TableRow.jsx b/frontend/src/AppBuilder/Widgets/Table/Components/TableRow.jsx index 8db79b1b5d..221e6b4594 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Components/TableRow.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Components/TableRow.jsx @@ -85,16 +85,20 @@ export const TableRow = React.memo( : '' }`} {...rowProps} - onClickCapture={async () => { + onClickCapture={() => { // toggleRowSelected will triggered useRededcuer function in useTable and in result will get the selectedFlatRows consisting row which are selected + const selectedRow = row.original; + const selectedRowId = row.id; + setExposedVariables({ selectedRow, selectedRowId }); + fireEvent('onRowClicked'); + }} + onClick={async () => { if (allowSelection) { await toggleRowSelected(row.id); } const selectedRow = row.original; const selectedRowId = row.id; - setExposedVariables({ selectedRow, selectedRowId }); mergeToTableDetails({ selectedRow, selectedRowId }); - fireEvent('onRowClicked'); }} onMouseOver={() => { if (hoverAdded) { From 365fa7090c4a8051d05a016b16e10a5221266f6f Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Mon, 28 Oct 2024 03:08:09 +0530 Subject: [PATCH 03/92] Event missing updated exposedVariables fix --- frontend/src/AppBuilder/Widgets/Modal.jsx | 31 +++++------ frontend/src/Editor/Components/Checkbox.jsx | 43 ++++++--------- frontend/src/Editor/Components/Datepicker.jsx | 41 +++++--------- frontend/src/Editor/Components/DropDown.jsx | 39 +++++--------- .../Components/DropdownV2/DropdownV2.jsx | 45 +++++++--------- .../MultiselectV2/MultiselectV2.jsx | 49 +++++++++-------- .../src/Editor/Components/NumberInput.jsx | 53 +++++++------------ .../src/Editor/Components/PasswordInput.jsx | 33 ++++++------ frontend/src/Editor/Components/TextInput.jsx | 35 ++++++------ frontend/src/Editor/Components/ToggleV2.jsx | 27 +++++----- 10 files changed, 167 insertions(+), 229 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index 7632770d91..a88aff630f 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -72,34 +72,22 @@ export const Modal = function Modal({ setShowModal(true); }, close: async function () { - setShowModal(false); setExposedVariable('show', false); + setShowModal(false); }, }; setExposedVariables(exposedVariables); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - if (isInitialRender.current) { - isInitialRender.current = false; - return; - } - - fireEvent(!showModal ? 'onClose' : 'onOpen'); - const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0]; - inputRef?.blur(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [showModal]); - function hideModal() { - setShowModal(false); setExposedVariable('show', false); + setShowModal(false); } function openModal() { - setShowModal(true); setExposedVariable('show', true); + setShowModal(true); } useEffect(() => { @@ -149,6 +137,19 @@ export const Modal = function Modal({ }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [showModal, modalHeight]); + + useEffect(() => { + if (isInitialRender.current) { + isInitialRender.current = false; + return; + } + + fireEvent(!showModal ? 'onClose' : 'onOpen'); + const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0]; + inputRef?.blur(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showModal]); + const backwardCompatibilityCheck = height == '34' || modalHeight != undefined ? true : false; const customStyles = { diff --git a/frontend/src/Editor/Components/Checkbox.jsx b/frontend/src/Editor/Components/Checkbox.jsx index 5b0021114c..3a9a8c1ebb 100644 --- a/frontend/src/Editor/Components/Checkbox.jsx +++ b/frontend/src/Editor/Components/Checkbox.jsx @@ -20,7 +20,6 @@ export const Checkbox = ({ const isMandatory = validation?.mandatory ?? false; const [defaultValue, setDefaultValue] = useState(defaultValueFromProperties); const [checked, setChecked] = useState(defaultValueFromProperties); - const [value, setValue] = React.useState(defaultValueFromProperties); const [userInteracted, setUserInteracted] = useState(false); const { label } = properties; @@ -33,14 +32,12 @@ export const Checkbox = ({ const [loading, setLoading] = useState(properties?.loadingState); const [disable, setDisable] = useState(disabledState || loadingState); const [visibility, setVisibility] = useState(properties.visibility); - const { isValid, validationError } = validate(checked); + const [validationStatus, setValidationStatus] = useState(validate(checked)); + const { isValid, validationError } = validationStatus; const toggleValue = (e) => { const isChecked = e.target.checked; - setChecked(isChecked); - setValue(isChecked); - - setExposedVariable('value', isChecked); + setInputValue(isChecked); if (isChecked) { fireEvent('onCheck'); } else { @@ -52,9 +49,8 @@ export const Checkbox = ({ useEffect(() => { if (isInitialRender.current) return; setDefaultValue(defaultValueFromProperties); - setChecked(defaultValueFromProperties); - setValue(defaultValueFromProperties); - setExposedVariable('value', defaultValueFromProperties); + setInputValue(defaultValueFromProperties); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValueFromProperties]); @@ -102,22 +98,14 @@ export const Checkbox = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [disable]); - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - useEffect(() => { const setCheckedAndNotify = async (status) => { - await setExposedVariable('value', status); + setInputValue(status); if (status) { fireEvent('onCheck'); } else { fireEvent('onUnCheck'); } - setChecked(status); - setValue(status); }; const exposedVariables = { @@ -138,10 +126,8 @@ export const Checkbox = ({ }, toggle: () => { setExposedVariable('toggle', async function () { - setExposedVariable('value', !checked); + setInputValue(!checked); fireEvent('onChange'); - setChecked(!checked); - setValue(!checked); setUserInteracted(true); }); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -154,10 +140,6 @@ export const Checkbox = ({ isValid: isValid, }; - setDefaultValue(defaultValueFromProperties); - setChecked(defaultValueFromProperties); - setValue(defaultValueFromProperties); - setExposedVariables(exposedVariables); isInitialRender.current = false; @@ -167,9 +149,7 @@ export const Checkbox = ({ const handleToggleChange = () => { const newCheckedState = !checked; - setChecked(newCheckedState); - setValue(newCheckedState); - setExposedVariable('value', newCheckedState); + setInputValue(newCheckedState); fireEvent('onChange'); if (newCheckedState) { fireEvent('onCheck'); @@ -179,6 +159,13 @@ export const Checkbox = ({ setUserInteracted(true); }; + const setInputValue = (value) => { + setChecked(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ value, isValid: validationStatus?.isValid }); + }; + const renderCheckBox = () => ( <>
{ setShowValidationError(true); - setDate(date); - const dateString = computeDateString(date); - setExposedVariable('value', dateString); + setInputValue(date); fireEvent('onSelect'); }; @@ -51,11 +52,9 @@ export const Datepicker = function Datepicker({ if (isInitialRender.current) return; const dateMomentInstance = defaultValue && moment(defaultValue, selectedDateFormat); if (dateMomentInstance && dateMomentInstance.isValid()) { - setDate(dateMomentInstance.toDate()); - setExposedVariable('value', defaultValue); + setInputValue(dateMomentInstance.toDate()); } else { - setDate(null); - setExposedVariable('value', undefined); + setInputValue(null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]); @@ -73,32 +72,20 @@ export const Datepicker = function Datepicker({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [disabledDates, format]); - const validationData = validate(computeDateString(date)); - const { isValid, validationError } = validationData; - useEffect(() => { - isInitialRender.current = false; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - - useEffect(() => { - const exposedVariables = { - isValid, - }; const dateMomentInstance = defaultValue && moment(defaultValue, selectedDateFormat); - if (dateMomentInstance && dateMomentInstance.isValid()) { - setDate(dateMomentInstance.toDate()); - exposedVariables.value = defaultValue; - } else { - setDate(null); - exposedVariables.value = undefined; - } - setExposedVariables(exposedVariables); + setInputValue(dateMomentInstance && dateMomentInstance.isValid() ? dateMomentInstance.toDate() : null); isInitialRender.current = false; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const setInputValue = (value) => { + setDate(value); + const validationStatus = validate(computeDateString(value)); + setValidationStatus(validationStatus); + setExposedVariables({ value: value ? computeDateString(value) : undefined, isValid: validationStatus?.isValid }); + }; + return (
(advanced ? findDefaultItem(schema) : value)); const [showValidationError, setShowValidationError] = useState(false); - const validationData = validate(value); - const { isValid, validationError } = validationData; + const [validationStatus, setValidationStatus] = useState(validate(value)); + const { isValid, validationError } = validationStatus; function findDefaultItem(schema) { const foundItem = schema?.find((item) => item?.default === true); return !hasVisibleFalse(foundItem?.value) ? foundItem?.value : undefined; @@ -59,15 +59,11 @@ export const DropDown = function DropDown({ } const setExposedItem = (value, index, onSelectFired = false) => { - setCurrentValue(value); + const selectedOptionLabel = index === undefined ? undefined : display_values?.[index]; + setInputValue(value, selectedOptionLabel); if (onSelectFired) { fireEvent('onSelect'); } - const exposedVariables = { - value, - selectedOptionLabel: index === undefined ? undefined : display_values?.[index], - }; - setExposedVariables(exposedVariables); }; function selectOption(value) { @@ -80,17 +76,6 @@ export const DropDown = function DropDown({ setExposedItem(undefined, undefined, true); } } - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('value', currentValue); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue]); useEffect(() => { if (isInitialRender.current) return; @@ -113,7 +98,7 @@ export const DropDown = function DropDown({ schema?.filter((item) => item?.visible)?.map((item) => item.label) ); if (hasVisibleFalse(currentValue)) { - setCurrentValue(findDefaultItem(schema)); + setInputValue(findDefaultItem(schema)); } } else setExposedVariable('optionLabels', display_values); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -137,9 +122,6 @@ export const DropDown = function DropDown({ }; setExposedVariables(exposedVariables); - if (hasVisibleFalse(currentValue)) { - setCurrentValue(findDefaultItem(schema)); - } isInitialRender.current = false; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -184,6 +166,13 @@ export const DropDown = function DropDown({ } }; + const setInputValue = (value, label) => { + setCurrentValue(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ value, isValid: validationStatus?.isValid, selectedOptionLabel: label }); + }; + const customStyles = { control: (provided, state) => ({ ...provided, @@ -276,10 +265,8 @@ export const DropDown = function DropDown({ onChange={(selectedOption, actionProps) => { setShowValidationError(true); if (actionProps.action === 'select-option') { - setCurrentValue(selectedOption.value); - setExposedVariable('value', selectedOption.value); + setInputValue(selectedOption.value, selectedOption.label); fireEvent('onSelect'); - setExposedVariable('selectedOptionLabel', selectedOption.label); } }} options={selectOptions} diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index a4b318eadf..1acddf4700 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -93,8 +93,8 @@ export const DropdownV2 = ({ const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const isMandatory = validation?.mandatory ?? false; const options = properties?.options; - const validationData = validate(currentValue); - const { isValid, validationError } = validationData; + const [validationStatus, setValidationStatus] = useState(validate(currentValue)); + const { isValid, validationError } = validationStatus; const ref = React.useRef(null); const [visibility, setVisibility] = useState(properties.visibility); const [isDropdownLoading, setIsDropdownLoading] = useState(dropdownLoadingState); @@ -132,7 +132,7 @@ export const DropdownV2 = ({ function selectOption(value) { const val = selectOptions.filter((option) => !option.isDisabled)?.find((option) => option.value === value); if (val) { - setCurrentValue(value); + setInputValue(value); fireEvent('onSelect'); } } @@ -162,10 +162,22 @@ export const DropdownV2 = ({ } }; + const setInputValue = (value) => { + setCurrentValue(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + const _selectedOption = selectOptions.find((option) => option.value === value); + setExposedVariables({ + value, + isValid: validationStatus?.isValid, + selectedOption: pick(_selectedOption, ['label', 'value']), + }); + }; + useEffect(() => { if (advanced) { - setCurrentValue(findDefaultItem(schema)); - } else setCurrentValue(value); + setInputValue(findDefaultItem(schema)); + } else setInputValue(value); // eslint-disable-next-line react-hooks/exhaustive-deps }, [advanced, value, JSON.stringify(schema)]); @@ -186,18 +198,6 @@ export const DropdownV2 = ({ // Exposed variables - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('value', currentValue); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue]); - - useEffect(() => { - const _selectedOption = selectOptions.find((option) => option.value === currentValue); - setExposedVariable('selectedOption', pick(_selectedOption, ['label', 'value'])); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue, JSON.stringify(selectOptions)]); - useEffect(() => { if (isInitialRender.current) return; const _options = selectOptions?.map(({ label, value }) => ({ label, value })); @@ -221,11 +221,6 @@ export const DropdownV2 = ({ setExposedVariable('searchText', searchInputValue); }, [searchInputValue]); - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - }, [isValid]); - useEffect(() => { if (isInitialRender.current) return; setExposedVariable('isVisible', properties.visibility); @@ -250,7 +245,7 @@ export const DropdownV2 = ({ const _options = selectOptions?.map(({ label, value }) => ({ label, value })); const exposedVariables = { clear: async function () { - setCurrentValue(null); + setInputValue(null); }, setVisibility: async function (value) { setVisibility(value); @@ -448,10 +443,10 @@ export const DropdownV2 = ({ value={selectOptions.filter((option) => option.value === currentValue)[0] ?? null} onChange={(selectedOption, actionProps) => { if (actionProps.action === 'clear') { - setCurrentValue(null); + setInputValue(null); } if (actionProps.action === 'select-option') { - setCurrentValue(selectedOption.value); + setInputValue(selectedOption.value); fireEvent('onSelect'); } setIsFocused(false); diff --git a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx index a3795e8672..960955de57 100644 --- a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx +++ b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx @@ -65,8 +65,10 @@ export const MultiselectV2 = ({ const isMandatory = validation?.mandatory ?? false; const multiselectRef = React.useRef(null); const labelRef = React.useRef(null); - const validationData = validate(selected?.length ? selected?.map((option) => option.value) : null); - const { isValid, validationError } = validationData; + const [validationStatus, setValidationStatus] = useState( + validate(selected?.length ? selected?.map((option) => option.value) : null) + ); + const { isValid, validationError } = validationStatus; const valueContainerRef = React.useRef(null); const [visibility, setVisibility] = useState(properties.visibility); const [isMultiSelectLoading, setIsMultiSelectLoading] = useState(multiSelectLoadingState); @@ -126,39 +128,32 @@ export const MultiselectV2 = ({ return false; } const onChangeHandler = (items, action) => { - setSelected(items); + setInputValue(items); if (action.action === 'select-option') { - setExposedVariable( - 'values', - items.map((item) => item.value) - ); fireEvent('onSelect'); } }; + useEffect(() => { let foundItem = findDefaultItem(values, advanced); - setSelected(foundItem); + setInputValue(foundItem); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectOptions]); useEffect(() => { let foundItem = findDefaultItem(values, advanced, true); - setSelected(foundItem); + setInputValue(foundItem); // eslint-disable-next-line react-hooks/exhaustive-deps }, [advanced, JSON.stringify(schema), JSON.stringify(values)]); useEffect(() => { if (isInitialRender.current) return; - setExposedVariable( - 'selectedOptions', - Array.isArray(selected) && selected?.map(({ label, value }) => ({ label, value })) - ); setExposedVariable( 'options', Array.isArray(selectOptions) && selectOptions?.map(({ label, value }) => ({ label, value })) ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(selected), selectOptions]); + }, [selectOptions]); useEffect(() => { if (isInitialRender.current) return; @@ -186,14 +181,11 @@ export const MultiselectV2 = ({ }, [isMandatory]); useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - }, [isValid]); + const defaultItems = findDefaultItem(values, advanced, true); - useEffect(() => { const exposedVariables = { clear: async function () { - setSelected([]); + setInputValue([]); }, setVisibility: async function (value) { setVisibility(value); @@ -210,7 +202,7 @@ export const MultiselectV2 = ({ isDisabled: disabledState, isMandatory: isMandatory, isValid: isValid, - selectedOptions: Array.isArray(selected) && selected?.map(({ label, value }) => ({ label, value })), + selectedOptions: Array.isArray(defaultItems) && defaultItems?.map(({ label, value }) => ({ label, value })), options: Array.isArray(selectOptions) && selectOptions?.map(({ label, value }) => ({ label, value })), }; setExposedVariables(exposedVariables); @@ -237,7 +229,7 @@ export const MultiselectV2 = ({ newSelected.push(...optionsToAdd); } }); - setSelected(newSelected); + setInputValue(newSelected); } }); @@ -247,7 +239,7 @@ export const MultiselectV2 = ({ // Check if array provided is a list of objects with value key const _value = value.map((val) => (isObject(val) && has(val, 'value') ? val.value : val)); const newSelected = selected.filter((option) => !_value.includes(option.value)); - setSelected(newSelected); + setInputValue(newSelected); } }); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -276,6 +268,17 @@ export const MultiselectV2 = ({ } }; + const setInputValue = (values) => { + setSelected(values); + const validationStatus = validate(values?.length ? values?.map((option) => option.value) : null); + setValidationStatus(validationStatus); + setExposedVariables({ + values: values.map((item) => item.value), + isValid: validationStatus?.isValid, + selectedOptions: Array.isArray(values) && values?.map(({ label, value }) => ({ label, value })), + }); + }; + useEffect(() => { document.addEventListener('mousedown', handleClickOutside, { capture: true }); return () => { @@ -485,7 +488,7 @@ export const MultiselectV2 = ({ showAllOption={showAllOption} isSelectAllSelected={isSelectAllSelected} setIsSelectAllSelected={setIsSelectAllSelected} - setSelected={setSelected} + setSelected={setInputValue} iconColor={iconColor} optionsLoadingState={optionsLoadingState && advanced} darkMode={darkMode} diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx index f5c1e79956..a8cf3ac5c5 100644 --- a/frontend/src/Editor/Components/NumberInput.jsx +++ b/frontend/src/Editor/Components/NumberInput.jsx @@ -49,7 +49,8 @@ export const NumberInput = function NumberInput({ const [loading, setLoading] = useState(loadingState); const [showValidationError, setShowValidationError] = useState(false); const [value, setValue] = React.useState(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces))); - const { isValid, validationError } = validate(value); + const [validationStatus, setValidationStatus] = useState(validate(value)); + const { isValid, validationError } = validationStatus; const [isFocused, setIsFocused] = useState(false); const inputRef = useRef(null); @@ -65,31 +66,22 @@ export const NumberInput = function NumberInput({ }, [label]); useEffect(() => { - setValue(Number(parseFloat(value).toFixed(properties.decimalPlaces))); + setInputValue(Number(parseFloat(value).toFixed(properties.decimalPlaces))); // eslint-disable-next-line react-hooks/exhaustive-deps }, [properties.decimalPlaces]); useEffect(() => { - setValue(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces))); + setInputValue(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces))); // eslint-disable-next-line react-hooks/exhaustive-deps }, [properties.value]); const handleBlur = (e) => { - setValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces))); + setInputValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces))); setShowValidationError(true); e.stopPropagation(); fireEvent('onBlur'); setIsFocused(false); }; - - useEffect(() => { - if (isInitialRender.current) return; - if (!isNaN(value)) { - setExposedVariable('value', value); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]); - useEffect(() => { if (isInitialRender.current) return; setExposedVariable('isMandatory', isMandatory); @@ -114,12 +106,6 @@ export const NumberInput = function NumberInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [disable]); - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - useEffect(() => { disable !== disabledState && setDisable(disabledState); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -144,14 +130,12 @@ export const NumberInput = function NumberInput({ setText: async function (text) { if (text) { const newValue = Number(parseFloat(text)); - setValue(newValue); - setExposedVariable('value', text); + setInputValue(newValue); fireEvent('onChange'); } }, clear: async function () { - setValue(''); - setExposedVariable('value', ''); + setInputValue(''); fireEvent('onChange'); }, setLoading: async function (loading) { @@ -244,14 +228,13 @@ export const NumberInput = function NumberInput({ // eslint-disable-next-line import/namespace const handleChange = (e) => { - setValue(Number(parseFloat(e.target.value))); if (e.target.value == '') { - setValue(null); - setExposedVariable('value', null); + setInputValue(null); fireEvent('onChange'); + } else { + setInputValue(Number(parseFloat(e.target.value))); } if (!isNaN(Number(parseFloat(e.target.value)))) { - setExposedVariable('value', Number(parseFloat(e.target.value))); fireEvent('onChange'); } }; @@ -260,22 +243,27 @@ export const NumberInput = function NumberInput({ e.preventDefault(); // Prevent the default button behavior (form submission, page reload) const newValue = (value || 0) + 1; - setValue(newValue); + setInputValue(newValue); if (!isNaN(newValue)) { - setExposedVariable('value', newValue); fireEvent('onChange'); } }; const handleDecrement = (e) => { e.preventDefault(); const newValue = (value || 0) - 1; - setValue(newValue); + setInputValue(newValue); if (!isNaN(newValue)) { - setExposedVariable('value', newValue); fireEvent('onChange'); } }; + const setInputValue = (value) => { + setValue(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ ...(!isNaN(value) && { value }), isValid: validationStatus?.isValid }); + }; + const loaderStyle = { right: direction === 'right' && @@ -375,8 +363,7 @@ export const NumberInput = function NumberInput({ autoComplete="off" onKeyUp={(e) => { if (e.key === 'Enter') { - setValue(e.target.value); - setExposedVariable('value', e.target.value); + setInputValue(e.target.value); fireEvent('onEnterPressed'); } }} diff --git a/frontend/src/Editor/Components/PasswordInput.jsx b/frontend/src/Editor/Components/PasswordInput.jsx index 96d70365ad..4729de9101 100644 --- a/frontend/src/Editor/Components/PasswordInput.jsx +++ b/frontend/src/Editor/Components/PasswordInput.jsx @@ -48,7 +48,8 @@ export const PasswordInput = function PasswordInput({ const [disable, setDisable] = useState(disabledState || loadingState); const [passwordValue, setPasswordValue] = useState(properties.value); const [visibility, setVisibility] = useState(properties.visibility); - const { isValid, validationError } = validate(passwordValue); + const [validationStatus, setValidationStatus] = useState(validate(passwordValue)); + const { isValid, validationError } = validationStatus; const [showValidationError, setShowValidationError] = useState(false); const [labelWidth, setLabelWidth] = useState(0); const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; @@ -152,12 +153,6 @@ export const PasswordInput = function PasswordInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [loadingState]); - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - useEffect(() => { if (isInitialRender.current) return; setExposedVariable('isMandatory', isMandatory); @@ -184,8 +179,7 @@ export const PasswordInput = function PasswordInput({ useEffect(() => { if (isInitialRender.current) return; - setPasswordValue(properties.value); - setExposedVariable('value', properties?.value ?? ''); + setInputValue(properties?.value || ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, [properties.value]); @@ -198,13 +192,11 @@ export const PasswordInput = function PasswordInput({ textInputRef.current.blur(); }, setText: async function (text) { - setPasswordValue(text); - setExposedVariable('value', text); + setInputValue(text); fireEvent('onChange'); }, clear: async function () { - setPasswordValue(''); - setExposedVariable('value', ''); + setInputValue(''); fireEvent('onChange'); }, setLoading: async function (loading) { @@ -228,7 +220,6 @@ export const PasswordInput = function PasswordInput({ value: properties?.value ?? '', }; - setPasswordValue(properties.value ?? ''); setExposedVariables(exposedVariables); isInitialRender.current = false; @@ -254,6 +245,14 @@ export const PasswordInput = function PasswordInput({ } return false; }); + + const setInputValue = (value) => { + setPasswordValue(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ value, isValid: validationStatus?.isValid }); + }; + const renderInput = () => ( <>
{ if (e.key === 'Enter') { - setPasswordValue(e.target.value); - setExposedVariable('value', e.target.value); + setInputValue(e.target.value); fireEvent('onEnterPressed'); } }} onChange={(e) => { - setPasswordValue(e.target.value); - setExposedVariable('value', e.target.value); + setInputValue(e.target.value); fireEvent('onChange'); }} onBlur={(e) => { diff --git a/frontend/src/Editor/Components/TextInput.jsx b/frontend/src/Editor/Components/TextInput.jsx index 7edbf89f60..f346589109 100644 --- a/frontend/src/Editor/Components/TextInput.jsx +++ b/frontend/src/Editor/Components/TextInput.jsx @@ -47,8 +47,8 @@ export const TextInput = function TextInput({ const [disable, setDisable] = useState(disabledState || loadingState); const [value, setValue] = useState(properties.value); const [visibility, setVisibility] = useState(properties.visibility); - // const isValid = true; // TODO: remove this and uncomment the below line - const { isValid, validationError } = validate(value); + const [validationStatus, setValidationStatus] = useState(validate(value)); + const { isValid, validationError } = validationStatus; const [showValidationError, setShowValidationError] = useState(false); const [labelWidth, setLabelWidth] = useState(0); @@ -155,19 +155,12 @@ export const TextInput = function TextInput({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [loadingState]); - useEffect(() => { - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); - useEffect(() => { if (isInitialRender.current) return; if (properties.value === undefined) { - setValue(''); - setExposedVariable('value', ''); + setInputValue(''); } else { - setValue(properties.value); - setExposedVariable('value', properties.value); + setInputValue(properties.value); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -200,13 +193,11 @@ export const TextInput = function TextInput({ useEffect(() => { const exposedVariables = { setText: async function (text) { - setValue(text); - setExposedVariable('value', text); + setInputValue(text); fireEvent('onChange'); }, clear: async function () { - setValue(''); - setExposedVariable('value', ''); + setInputValue(''); fireEvent('onChange'); }, setFocus: async function () { @@ -241,13 +232,19 @@ export const TextInput = function TextInput({ isVisible: visibility, isDisabled: disable, }; - setValue(properties.value); setExposedVariables(exposedVariables); isInitialRender.current = false; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const setInputValue = (value) => { + setValue(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ value, isValid: validationStatus?.isValid }); + }; + const iconName = styles.icon; // Replace with the name of the icon you want // eslint-disable-next-line import/namespace const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName]; @@ -320,14 +317,12 @@ export const TextInput = function TextInput({ } validation-without-icon`} onKeyUp={(e) => { if (e.key === 'Enter') { - setValue(e.target.value); - setExposedVariable('value', e.target.value); + setInputValue(e.target.value); fireEvent('onEnterPressed'); } }} onChange={(e) => { - setValue(e.target.value); - setExposedVariable('value', e.target.value); + setInputValue(e.target.value); fireEvent('onChange'); }} onBlur={(e) => { diff --git a/frontend/src/Editor/Components/ToggleV2.jsx b/frontend/src/Editor/Components/ToggleV2.jsx index 11569828c7..752d14a361 100644 --- a/frontend/src/Editor/Components/ToggleV2.jsx +++ b/frontend/src/Editor/Components/ToggleV2.jsx @@ -105,7 +105,8 @@ export const ToggleSwitchV2 = ({ const [on, setOn] = useState(Boolean(defaultValue)); const label = properties.label; const isMandatory = validation?.mandatory ?? false; - const { isValid, validationError } = validate(on); + const [validationStatus, setValidationStatus] = useState(validate(on)); + const { isValid, validationError } = validationStatus; const [showValidationError, setShowValidationError] = useState(true); const [loading, setLoading] = useState(properties?.loadingState); const [disable, setDisable] = useState(properties.disabledState || properties.loadingState); @@ -123,17 +124,23 @@ export const ToggleSwitchV2 = ({ }; // Exposing the initially set false value once on load + const setInputValue = (value) => { + setOn(value); + const validationStatus = validate(value); + setValidationStatus(validationStatus); + setExposedVariables({ value, isValid: validationStatus?.isValid }); + }; + useEffect(() => { if (isInitialRender.current) return; - setExposedVariable('value', defaultValue); // eslint-disable-next-line react-hooks/exhaustive-deps - setOn(defaultValue); + setInputValue(defaultValue); // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]); const toggle = () => { - setOn(!on); + setInputValue(!on); setUserInteracted(true); }; @@ -180,17 +187,11 @@ export const ToggleSwitchV2 = ({ setExposedVariable('isDisabled', disable); // eslint-disable-next-line react-hooks/exhaustive-deps }, [disable]); - useEffect(() => { - if (isInitialRender.current) return; - setExposedVariable('isValid', isValid); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isValid]); useEffect(() => { const exposedVariables = { setValue: async function (value) { - setOn(value); - setExposedVariable('value', value); + setInputValue(value); setUserInteracted(true); }, setVisibility: async function (state) { @@ -214,15 +215,13 @@ export const ToggleSwitchV2 = ({ value: defaultValue, }; setExposedVariables(exposedVariables); - setOn(defaultValue); isInitialRender.current = false; }, []); useEffect(() => { setExposedVariable('toggle', async function () { - setExposedVariable('value', !on); + setInputValue(!on); fireEvent('onChange'); - setOn(!on); setUserInteracted(true); }); // eslint-disable-next-line react-hooks/exhaustive-deps From 4b0a640b2893cad60d657321dcec875e24d9e7f6 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 02:42:58 +0530 Subject: [PATCH 04/92] Fixed checkbox toggle issue --- frontend/src/Editor/Components/Checkbox.jsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/src/Editor/Components/Checkbox.jsx b/frontend/src/Editor/Components/Checkbox.jsx index 3a9a8c1ebb..54ad39d2c6 100644 --- a/frontend/src/Editor/Components/Checkbox.jsx +++ b/frontend/src/Editor/Components/Checkbox.jsx @@ -98,6 +98,16 @@ export const Checkbox = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [disable]); + useEffect(() => { + if (isInitialRender.current) return; + setExposedVariable('toggle', async function () { + setInputValue(!checked); + fireEvent('onChange'); + setUserInteracted(true); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [checked]); + useEffect(() => { const setCheckedAndNotify = async (status) => { setInputValue(status); @@ -125,12 +135,9 @@ export const Checkbox = ({ setExposedVariable('isDisabled', disable); }, toggle: () => { - setExposedVariable('toggle', async function () { - setInputValue(!checked); - fireEvent('onChange'); - setUserInteracted(true); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps + setInputValue(!checked); + fireEvent('onChange'); + setUserInteracted(true); }, label: label, isMandatory: isMandatory, From c1f76a58ad7ca21ba22cf83c1a0fd72988d78c8b Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 14:42:07 +0530 Subject: [PATCH 05/92] Fixed issue causing table columns to autogenerate on deleting them --- frontend/src/AppBuilder/Widgets/Table/Table.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index d6966192cb..559ca42267 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -581,7 +581,7 @@ export const Table = React.memo( ); useDynamicColumn && setGeneratedColumn(generatedColumnFromData); } - }, [tableData, JSON.stringify(dynamicColumn)]); + }, [JSON.stringify(tableData), JSON.stringify(dynamicColumn)]); const computedStyles = { // width: `${width}px`, From ef402992f3ced1124e21d9b391a5caba3feb9c27 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 1 Nov 2024 02:39:01 +0530 Subject: [PATCH 06/92] Fixed string & number as currentTab causing issues --- frontend/src/AppBuilder/Widgets/Tabs.jsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Tabs.jsx b/frontend/src/AppBuilder/Widgets/Tabs.jsx index 5a78a07809..3a93fa698b 100644 --- a/frontend/src/AppBuilder/Widgets/Tabs.jsx +++ b/frontend/src/AppBuilder/Widgets/Tabs.jsx @@ -69,14 +69,14 @@ export const Tabs = function Tabs({ }, [parsedDefaultTab]); useEffect(() => { - const currentTabData = parsedTabs.filter((tab) => tab.id === currentTab); + const currentTabData = parsedTabs.filter((tab) => tab.id == currentTab); setBgColor(currentTabData[0]?.backgroundColor ? currentTabData[0]?.backgroundColor : darkMode ? '#324156' : '#fff'); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentTab, darkMode]); function computeTabDisplay(componentId, id) { let tabVisibility = 'none'; - if (id !== currentTab) { + if (id != currentTab) { return tabVisibility; } @@ -87,14 +87,13 @@ export const Tabs = function Tabs({ } } - return id === currentTab ? 'block' : 'none'; + return id == currentTab ? 'block' : 'none'; } useEffect(() => { const exposedVariables = { setTab: async function (id) { - id = typeof id === 'number' ? String(id) : id; - if (id && currentTab !== id) { + if (currentTab != id) { setCurrentTab(id); setExposedVariable('currentTab', id); fireEvent('onTabSwitch'); @@ -134,7 +133,7 @@ export const Tabs = function Tabs({ function shouldRenderTabContent(tab) { if (parsedRenderOnlyActiveTab) { - return tab.id === currentTab; + return tab.id == currentTab; } return true; // Render by default if no specific conditions are met } @@ -162,7 +161,7 @@ export const Tabs = function Tabs({ className="nav-item" style={{ opacity: tab?.disabled && '0.5', width: tabWidth == 'split' && equalSplitWidth + '%' }} onClick={() => { - if (currentTab === tab.id) return; + if (currentTab == tab.id) return; !tab?.disabled && setCurrentTab(tab.id); !tab?.disabled && setExposedVariable('currentTab', tab.id); @@ -193,7 +192,7 @@ export const Tabs = function Tabs({
{ - if (currentTab === tab.id) { + if (currentTab == tab.id) { parentRef.current = newCurrent; } }} @@ -202,7 +201,7 @@ export const Tabs = function Tabs({ > {shouldRenderTabContent(tab) && renderTabContent(id, tab)} - {/* {tab.id === currentTab && } */} + {/* {tab.id == currentTab && } */}
))}
From 65b4be0aac35235e0ac2d06b7221e69af4091a9d Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Mon, 4 Nov 2024 10:26:29 +0530 Subject: [PATCH 07/92] fix table going in infinite loop when using dynamic columns without id (#2527) --- frontend/src/AppBuilder/Widgets/Table/Table.jsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 559ca42267..8bbe06ffd6 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -579,7 +579,22 @@ export const Table = React.memo( properties.autogenerateColumns ?? false, id ); - useDynamicColumn && setGeneratedColumn(generatedColumnFromData); + if (useDynamicColumn) { + const dynamicColumnHasId = dynamicColumn && dynamicColumn.every((column) => 'id' in column); + if (!dynamicColumnHasId) { + // if dynamic columns do not have an id then we need to manually compare the generated columns with the columns in the state because the id that we generate for columns without id is a uuid and it will be different every time + const generatedColumnsWithoutIds = generatedColumnFromData.map(({ id, ...rest }) => ({ + ...rest, + })); + const columnsFromStateWithoutIds = generatedColumn.map(({ id, ...rest }) => ({ + ...rest, + })); + !isEqual(generatedColumnsWithoutIds, columnsFromStateWithoutIds) && + setGeneratedColumn(generatedColumnFromData); + return; + } + setGeneratedColumn(generatedColumnFromData); + } } }, [JSON.stringify(tableData), JSON.stringify(dynamicColumn)]); From b12aecb5ad5585a2d19c834041f019f04b6fc3c0 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 02:10:48 +0530 Subject: [PATCH 08/92] Fixed undo redo breaking on setting component properties --- frontend/src/AppBuilder/Widgets/Table/Table.jsx | 5 ++++- .../Widgets/Table/columns/autogenerateColumns.js | 12 ++++++++++-- .../src/AppBuilder/_stores/slices/undoRedoSlice.js | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 8bbe06ffd6..714f2dd640 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -139,6 +139,7 @@ export const Table = React.memo( const updatedDataReference = useRef([]); const preSelectRow = useRef(false); const initialPageCountRef = useRef(null); + const isInitialRender = useRef(true); const allAppEvents = useEvents(); // const { events: allAppEvents } = useAppInfo(); const tableEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id); @@ -577,8 +578,10 @@ export const Table = React.memo( dynamicColumn, setComponentProperty, properties.autogenerateColumns ?? false, - id + id, + isInitialRender.current ); + isInitialRender.current = false; if (useDynamicColumn) { const dynamicColumnHasId = dynamicColumn && dynamicColumn.every((column) => 'id' in column); if (!dynamicColumnHasId) { diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 235205dbb3..e093bdeac5 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -9,7 +9,8 @@ export default function autogenerateColumns( dynamicColumn = [], setProperty, generateNestedColumns, - id + id, + isInitialRender ) { if (useDynamicColumn) { if (dynamicColumn.length > 0 && dynamicColumn[0].name) { @@ -107,7 +108,14 @@ export default function autogenerateColumns( finalKeys.includes(column?.key || column?.name) ); - setTimeout(() => setProperty(id, 'columns', finalColumns, 'properties'), 10); + setTimeout( + () => + setProperty(id, 'columns', finalColumns, 'properties', undefined, undefined, undefined, { + skipUndoRedo: isInitialRender, + saveAfterAction: true, + }), + 10 + ); } const dataTypeToColumnTypeMapping = { diff --git a/frontend/src/AppBuilder/_stores/slices/undoRedoSlice.js b/frontend/src/AppBuilder/_stores/slices/undoRedoSlice.js index ec8e9bbf58..901baed1f3 100644 --- a/frontend/src/AppBuilder/_stores/slices/undoRedoSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/undoRedoSlice.js @@ -106,6 +106,7 @@ export const createUndoRedoSlice = (set, get) => { componenetPropertiesToUpdate.paramType, componenetPropertiesToUpdate.attr, undefined, + undefined, { skipUndoRedo: true } ); } From dd1620a3bd1ef21c570d98cedc9dc72ddc8c12ee Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 02:15:00 +0530 Subject: [PATCH 09/92] Fixed issue causing entire subcontainer components to not be undoed/redoed together --- .../_stores/slices/componentsSlice.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index d5b0afa9d4..3f36c005f7 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -743,9 +743,12 @@ export const createComponentsSlice = (set, get) => ({ deleteComponentNameIdMapping(oldName, moduleId); } updateComponentDependencyGraph(moduleId, newComponent); - const parentId = newComponent.component.parent || 'canvas'; - set( - withUndoRedo((state) => { + }); + set( + withUndoRedo((state) => { + newComponents.forEach((newComponent) => { + const parentId = newComponent.component.parent || 'canvas'; + if (!state.containerChildrenMapping[parentId]) { state.containerChildrenMapping[parentId] = []; } @@ -754,15 +757,14 @@ export const createComponentsSlice = (set, get) => ({ } const page = state.modules[moduleId].pages[state.currentPageIndex]; page.components[newComponent.id] = newComponent; - }, skipUndoRedo), - false, - 'addComponentToCurrentPage' - ); - if (index === 0) { - //incase of multiple components, only first one will be selected since it will be the parent component - get().setSelectedComponents([newComponent.id]); - } - }); + }); + }, skipUndoRedo), + false, + 'addComponentToCurrentPage' + ); + + //incase of multiple components, only first one will be selected since it will be the parent component + get().setSelectedComponents([newComponents[0].id]); if (saveAfterAction) { saveComponentChanges(diff, 'components', 'create') From 8515d226bc2c1ba8d825219e940ad148b0bce72d Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 16:20:00 +0530 Subject: [PATCH 10/92] Minor fixes for table autogenerate column --- frontend/src/AppBuilder/Widgets/Table/Table.jsx | 5 +---- .../AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 714f2dd640..8bbe06ffd6 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -139,7 +139,6 @@ export const Table = React.memo( const updatedDataReference = useRef([]); const preSelectRow = useRef(false); const initialPageCountRef = useRef(null); - const isInitialRender = useRef(true); const allAppEvents = useEvents(); // const { events: allAppEvents } = useAppInfo(); const tableEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id); @@ -578,10 +577,8 @@ export const Table = React.memo( dynamicColumn, setComponentProperty, properties.autogenerateColumns ?? false, - id, - isInitialRender.current + id ); - isInitialRender.current = false; if (useDynamicColumn) { const dynamicColumnHasId = dynamicColumn && dynamicColumn.every((column) => 'id' in column); if (!dynamicColumnHasId) { diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index e093bdeac5..334f115d4e 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -9,8 +9,7 @@ export default function autogenerateColumns( dynamicColumn = [], setProperty, generateNestedColumns, - id, - isInitialRender + id ) { if (useDynamicColumn) { if (dynamicColumn.length > 0 && dynamicColumn[0].name) { @@ -111,7 +110,7 @@ export default function autogenerateColumns( setTimeout( () => setProperty(id, 'columns', finalColumns, 'properties', undefined, undefined, undefined, { - skipUndoRedo: isInitialRender, + skipUndoRedo: true, saveAfterAction: true, }), 10 From 43618a2186824e911fd71f121e6089f507f4895c Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 6 Nov 2024 16:38:50 +0530 Subject: [PATCH 11/92] Missing changes added --- .../src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 334f115d4e..98faa12da0 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -109,7 +109,7 @@ export default function autogenerateColumns( setTimeout( () => - setProperty(id, 'columns', finalColumns, 'properties', undefined, undefined, undefined, { + setProperty(id, 'columns', finalColumns, 'properties', 'value', false, 'canvas', { skipUndoRedo: true, saveAfterAction: true, }), From e81a0b1be961240859414b5244dccb2f3336022a Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 7 Nov 2024 14:08:58 +0530 Subject: [PATCH 12/92] Fixed listview breaking --- .../_stores/slices/componentsSlice.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 3f36c005f7..d5b0afa9d4 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -743,12 +743,9 @@ export const createComponentsSlice = (set, get) => ({ deleteComponentNameIdMapping(oldName, moduleId); } updateComponentDependencyGraph(moduleId, newComponent); - }); - set( - withUndoRedo((state) => { - newComponents.forEach((newComponent) => { - const parentId = newComponent.component.parent || 'canvas'; - + const parentId = newComponent.component.parent || 'canvas'; + set( + withUndoRedo((state) => { if (!state.containerChildrenMapping[parentId]) { state.containerChildrenMapping[parentId] = []; } @@ -757,14 +754,15 @@ export const createComponentsSlice = (set, get) => ({ } const page = state.modules[moduleId].pages[state.currentPageIndex]; page.components[newComponent.id] = newComponent; - }); - }, skipUndoRedo), - false, - 'addComponentToCurrentPage' - ); - - //incase of multiple components, only first one will be selected since it will be the parent component - get().setSelectedComponents([newComponents[0].id]); + }, skipUndoRedo), + false, + 'addComponentToCurrentPage' + ); + if (index === 0) { + //incase of multiple components, only first one will be selected since it will be the parent component + get().setSelectedComponents([newComponent.id]); + } + }); if (saveAfterAction) { saveComponentChanges(diff, 'components', 'create') From 220beec2a2d671326f93b4d44967b9b4209418b5 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 7 Nov 2024 14:53:17 +0530 Subject: [PATCH 13/92] Fixed button group unselect and some css --- .../src/AppBuilder/WidgetManager/widgets/buttonGroup.js | 4 ++-- frontend/src/Editor/Components/ButtonGroup.jsx | 9 ++++++++- server/src/helpers/widget-config/buttonGroup.js | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js index 65b7e77807..c0fa889dd5 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js @@ -146,8 +146,8 @@ export const buttonGroupConfig = { visibility: { value: '{{true}}' }, borderRadius: { value: '{{4}}' }, disabledState: { value: '{{false}}' }, - selectedTextColor: { value: '' }, - selectedBackgroundColor: { value: '' }, + selectedTextColor: { value: '#FFFFFF' }, + selectedBackgroundColor: { value: '#4368E3' }, }, }, }; diff --git a/frontend/src/Editor/Components/ButtonGroup.jsx b/frontend/src/Editor/Components/ButtonGroup.jsx index 9711820bc7..7364348271 100644 --- a/frontend/src/Editor/Components/ButtonGroup.jsx +++ b/frontend/src/Editor/Components/ButtonGroup.jsx @@ -34,6 +34,12 @@ export const ButtonGroup = function Button({ display: visibility ? '' : 'none', }; + const disabledStyles = { + opacity: 0.5, + pointerEvents: 'none', + cursor: 'not-allowed', + }; + const [defaultActive, setDefaultActive] = useState(defaultSelected); const [data, setData] = useState(values); @@ -62,7 +68,7 @@ export const ButtonGroup = function Button({ const buttonClick = (index) => { if (defaultActive?.includes(values[index]) && multiSelection) { - const copyDefaultActive = defaultActive; + const copyDefaultActive = [...defaultActive]; copyDefaultActive?.splice(copyDefaultActive?.indexOf(values[index]), 1); setDefaultActive(copyDefaultActive); setExposedVariable('selected', copyDefaultActive.join(',')); @@ -100,6 +106,7 @@ export const ButtonGroup = function Button({ color: defaultActive?.includes(values[index]) ? selectedTextColor : textColor, transition: 'all .1s ease', boxShadow, + ...(disabledState && disabledStyles), }} key={index} disabled={disabledState} diff --git a/server/src/helpers/widget-config/buttonGroup.js b/server/src/helpers/widget-config/buttonGroup.js index 65b7e77807..c0fa889dd5 100644 --- a/server/src/helpers/widget-config/buttonGroup.js +++ b/server/src/helpers/widget-config/buttonGroup.js @@ -146,8 +146,8 @@ export const buttonGroupConfig = { visibility: { value: '{{true}}' }, borderRadius: { value: '{{4}}' }, disabledState: { value: '{{false}}' }, - selectedTextColor: { value: '' }, - selectedBackgroundColor: { value: '' }, + selectedTextColor: { value: '#FFFFFF' }, + selectedBackgroundColor: { value: '#4368E3' }, }, }, }; From 6dda1d25830e3a20c3442f532add7630b3aa549f Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 30 Oct 2024 15:00:31 +0530 Subject: [PATCH 14/92] Fixed form breaking on wrong input --- frontend/src/AppBuilder/Widgets/Form/FormUtils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/Widgets/Form/FormUtils.js b/frontend/src/AppBuilder/Widgets/Form/FormUtils.js index bf4a8cd647..8b9c345c13 100644 --- a/frontend/src/AppBuilder/Widgets/Form/FormUtils.js +++ b/frontend/src/AppBuilder/Widgets/Form/FormUtils.js @@ -77,6 +77,7 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') { const itemType = typeResolver(value?.type); if (itemType) { uiComponentsDraft.push(getComponentDefinition('Text')); + uiComponentsDraft.push(getComponentDefinition(itemType)); //only add if there is a valid item type } else { // useCurrentStateStore.getState().actions.setErrors({ @@ -89,8 +90,8 @@ export function generateUIComponents(JSONSchema, advanced, componentName = '') { // }, // }); uiComponentsDraft.push(undefined); + uiComponentsDraft.push(undefined); } - uiComponentsDraft.push(getComponentDefinition(itemType)); }); Object.entries(JSONSchema?.properties).forEach(([key, value], index) => { if (uiComponentsDraft?.length > 0 && uiComponentsDraft[index * 2 + 1]) { From dda026a00f56b2b764fb755ea1e94a8bd061780b Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Thu, 7 Nov 2024 18:27:51 +0530 Subject: [PATCH 15/92] fix: page settings on public app --- frontend/src/AppBuilder/_hooks/useAppData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index bb745f2568..bc33e7bba9 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -193,7 +193,7 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId } ); setPages(pages, moduleId); - setPageSettings(deepCamelCase(appData?.editing_version?.page_settings)); + setPageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings)); // set starting page as homepage initially let startingPage = appData.pages.find((page) => page.id === homePageId); From 67b3ea2edbd54664e306d480ba94a82ef2d9b192 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:09:35 +0530 Subject: [PATCH 16/92] Array property added to dependency graph --- .../_stores/slices/componentsSlice.js | 224 +++++++++++++++--- frontend/src/AppBuilder/_stores/utils.js | 31 +++ 2 files changed, 216 insertions(+), 39 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index d5b0afa9d4..e5b10d2120 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -4,12 +4,12 @@ import { resolveDynamicValues, // extractAndReplaceReferencesFromString, checkSubstringRegex, + hasArrayNotation, + parsePropertyPath, } from '@/AppBuilder/_stores/utils'; import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast'; -import { componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; -import { v4 as uuidv4 } from 'uuid'; -import _, { cloneDeep, merge } from 'lodash'; +import { cloneDeep, merge, set as lodashSet } from 'lodash'; import { computeComponentName, getAllChildComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils'; import { pageConfig } from '@/AppBuilder/RightSideBar/PageSettingsTab/pageConfig'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; @@ -243,7 +243,8 @@ export const createComponentsSlice = (set, get) => ({ property, value, component, - componentResolvedValues = {}, // If componentResolvedValues is null, then it is from a setComponentProperty call + componentResolvedValues = {}, + updatePassedValue = true, moduleId ) => { const { @@ -281,7 +282,7 @@ export const createComponentsSlice = (set, get) => ({ allRefs.push(customResolvablePath); } } - if (componentResolvedValues !== null) + if (updatePassedValue) setAllValueToComponent( componentDetails, valueWithBrackets, @@ -294,7 +295,7 @@ export const createComponentsSlice = (set, get) => ({ return { updatedValue: valueWithId, allRefs, unResolvedValue: valueWithBrackets, componentResolvedValues }; } else { - if (componentResolvedValues !== null) + if (updatePassedValue) setAllValueToComponent( componentDetails, value, @@ -317,7 +318,7 @@ export const createComponentsSlice = (set, get) => ({ componentResolvedValues = {}, moduleId ) => { - const { getAllExposedValues } = get(); + const { getAllExposedValues, getComponentTypeFromId } = get(); const { componentId, paramType, property } = componentDetails; const length = Object.keys(customResolvables).length; if (length === 0) { @@ -334,12 +335,31 @@ export const createComponentsSlice = (set, get) => ({ if (!componentResolvedValues[componentId][index][paramType]) { componentResolvedValues[componentId][index][paramType] = {}; } - componentResolvedValues[componentId][index][paramType][property] = resolvedValue; + if (hasArrayNotation(property)) { + const keys = parsePropertyPath(property); + lodashSet( + componentResolvedValues, + [componentId, index, paramType, ...keys], + getComponentTypeFromId(componentId) === 'Table' ? value : resolvedValue + ); + } else { + componentResolvedValues[componentId][index][paramType][property] = resolvedValue; + } } else { if (!componentResolvedValues[componentId][paramType]) { componentResolvedValues[componentId][paramType] = {}; } - componentResolvedValues[componentId][paramType][property] = resolvedValue; + + if (hasArrayNotation(property)) { + const keys = parsePropertyPath(property); + lodashSet( + componentResolvedValues, + [componentId, paramType, ...keys], + getComponentTypeFromId(componentId) === 'Table' ? value : resolvedValue + ); + } else { + componentResolvedValues[componentId][paramType][property] = resolvedValue; + } } } else { // Loop all the index and set the resolved value @@ -564,6 +584,63 @@ export const createComponentsSlice = (set, get) => ({ }; }, + // This function checks whether the property value is an array or not and then resolves the value accordingly + // Cases like Table column, Dropdown options, etc. + checkValueAndResolve: ( + componentId, + paramType, + property, + value, + component, + resolvedComponentValues, + updatePassedValue = true, + moduleId + ) => { + const { updateResolvedValues, generateDependencyGraphForRefs } = get(); + const updatedPropertyValue = cloneDeep(value); + if (Array.isArray(value)) { + value.forEach((val, index) => { + Object.entries(val).forEach(([key, keyValue]) => { + const propertyWithArrayValue = `${property}[${index}].${key}`; + const keys = [key]; + if (keyValue?.value) { + keys.push('value'); + } + const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues( + componentId, + paramType, + propertyWithArrayValue, + keyValue?.value ?? keyValue, + component, + resolvedComponentValues, + updatePassedValue, + moduleId + ); + lodashSet(updatedPropertyValue, [index, ...keys], updatedValue); + if (allRefs.length) { + generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + } + }); + }); + return { updatedValue: updatedPropertyValue }; + } else { + const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues( + componentId, + paramType, + property, + value, + component, + resolvedComponentValues, + updatePassedValue, + moduleId + ); + if (allRefs.length) { + generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue); + } + return { allRefs, unResolvedValue, updatedValue }; + } + }, + updateDependencyGraphAndResolvedValues: ( moduleId, componentId, @@ -572,22 +649,20 @@ export const createComponentsSlice = (set, get) => ({ resolvedComponentValues = {}, paramType ) => { - const { updateResolvedValues, generateDependencyGraphForRefs, setAllValueToComponent } = get(); + const { checkValueAndResolve, setAllValueToComponent } = get(); if (component.definition[paramType] === undefined) return; Object.entries(component.definition[paramType]).forEach(([property, value]) => { if (!value?.skipResolve) { - const { allRefs, unResolvedValue } = updateResolvedValues( + checkValueAndResolve( componentId, paramType, property, value?.value, component, resolvedComponentValues, + true, moduleId ); - if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue); - } } else { const componentDetails = { componentId, paramType, property }; setAllValueToComponent(componentDetails, value?.value, false, null, {}, resolvedComponentValues, moduleId); @@ -925,11 +1000,10 @@ export const createComponentsSlice = (set, get) => ({ saveComponentChanges, withUndoRedo, getComponentTypeFromId, - updateResolvedValues, - generateDependencyGraphForRefs, setResolvedComponent, getComponentDefinition, currentLayout, + checkValueAndResolve, } = get(); let hasParentChanged = false; let oldParentId; @@ -1003,19 +1077,16 @@ export const createComponentsSlice = (set, get) => ({ objectsToUpdate.forEach((paramType) => { if (component.definition[paramType]) { Object.entries(component.definition[paramType]).forEach(([property, value]) => { - const { allRefs, unResolvedValue } = updateResolvedValues( + checkValueAndResolve( componentId, paramType, property, value.value, component, resolvedComponentValues, + true, moduleId ); - - if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true); - } }); } }); @@ -1066,22 +1137,65 @@ export const createComponentsSlice = (set, get) => ({ removeDependency, getComponentDefinition, setValueToComponent, + checkValueAndResolve, + getResolvedComponent, + setResolvedComponent, } = get(); const { component } = getComponentDefinition(componentId, moduleId); + const oldValue = component.definition[paramType][property]; + + if (Array.isArray(oldValue?.value)) { + const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + const { updatedValue } = checkValueAndResolve( + componentId, + paramType, + property, + value, + component, + resolvedComponent, + true, + moduleId + ); + setResolvedComponent(componentId, resolvedComponent[componentId], moduleId); + + // If the value is not changed, return + if (oldValue?.[attr] === updatedValue || oldValue === updatedValue) return; + + set( + withUndoRedo((state) => { + const pageComponent = state.modules[moduleId].pages[currentPageIndex].components[componentId].component; + lodashSet(pageComponent, ['definition', paramType, property, attr], updatedValue); + }, skipUndoRedo), + false, + 'setComponentProperty' + ); + + const diff = { + [componentId]: { component: get().modules[moduleId].pages[currentPageIndex].components[componentId].component }, + }; + + if (saveAfterAction) { + const currentMode = get().currentMode; + if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update'); + + get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update'); + } + return; + } + // Update the value and get new dependencies const { updatedValue, allRefs, unResolvedValue } = attr === 'value' && !skipResolve - ? updateResolvedValues(componentId, paramType, property, value, component, null, moduleId) + ? updateResolvedValues(componentId, paramType, property, value, component, null, false, moduleId) : { updatedValue: value, allRefs: [], unResolvedValue: value }; // If the value is not changed, return - const oldValue = component.definition[paramType][property]; if (oldValue?.[attr] === updatedValue || oldValue === updatedValue) return; set( withUndoRedo((state) => { const pageComponent = state.modules[moduleId].pages[currentPageIndex].components[componentId].component; - _.set(pageComponent, ['definition', paramType, property, attr], updatedValue); + lodashSet(pageComponent, ['definition', paramType, property, attr], updatedValue); }, skipUndoRedo), false, 'setComponentProperty' @@ -1130,9 +1244,8 @@ export const createComponentsSlice = (set, get) => ({ const { currentPageIndex, saveComponentChanges, - updateResolvedValues, + checkValueAndResolve, getComponentDefinition, - generateDependencyGraphForRefs, getComponentTypeFromId, setResolvedComponent, withUndoRedo, @@ -1189,19 +1302,16 @@ export const createComponentsSlice = (set, get) => ({ objectsToUpdate.forEach((paramType) => { if (component.definition[paramType]) { Object.entries(component.definition[paramType]).forEach(([property, value]) => { - const { allRefs, unResolvedValue } = updateResolvedValues( + checkValueAndResolve( componentId, paramType, property, value.value, component, resolvedComponentValues, + true, moduleId ); - - if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true); - } }); } }); @@ -1427,6 +1537,8 @@ export const createComponentsSlice = (set, get) => ({ getNodeData, getEntityResolvedValueLength, updateChildComponentResolvedValues, + getComponentTypeFromId, + getResolvedComponent, } = get(); const dependecies = getDependencies(path, moduleId); if (dependecies?.length) { @@ -1436,7 +1548,8 @@ export const createComponentsSlice = (set, get) => ({ if (itemsLength) { updateChildComponentResolvedValues(dependency, path, itemsLength, moduleId); } else { - const [entityType, entityId, type, key] = dependency.split('.'); + const [entityType, entityId, type, ...keys] = dependency.split('.'); + const key = keys.join('.'); const unResolvedValue = getNodeData(dependency); const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(), {}, false, []); @@ -1455,13 +1568,46 @@ export const createComponentsSlice = (set, get) => ({ ? get().debugger.validateProperty(entityId, type, key, resolvedValue) : resolvedValue; - set( - (state) => { - state.resolvedStore.modules[moduleId][entityType][entityId][type][key] = validatedValue; - }, - false, - 'updateDependencyValues' - ); + // logic to handle the key like options[0].visible. It will resolve the visible directly and update the resolved store + if (hasArrayNotation(key)) { + const keys = parsePropertyPath(key); + // Triggering a re-render of the table component if any of the dependent component is updated + // This is done to calculate the callValues in the table component + // Need to find a better way to handle this + if (getComponentTypeFromId(entityId, moduleId) === 'Table') { + set( + (state) => { + lodashSet( + state.resolvedStore.modules[moduleId][entityType][entityId], + ['properties', 'shouldRender'], + (getResolvedComponent(entityId)?.['properties']?.['shouldRender'] ?? 0) + 1 + ); + }, + false, + 'updateDependencyValues' + ); + } else { + set( + (state) => { + lodashSet( + state.resolvedStore.modules[moduleId][entityType][entityId], + [type, ...keys], + getComponentTypeFromId(entityId, moduleId) === 'Table' ? unResolvedValue + ' ' : validatedValue + ); + }, + false, + 'updateDependencyValues' + ); + } + } else { + set( + (state) => { + state.resolvedStore.modules[moduleId][entityType][entityId][type][key] = validatedValue; + }, + false, + 'updateDependencyValues' + ); + } } } }); diff --git a/frontend/src/AppBuilder/_stores/utils.js b/frontend/src/AppBuilder/_stores/utils.js index 20570b9b36..cc598af3a9 100644 --- a/frontend/src/AppBuilder/_stores/utils.js +++ b/frontend/src/AppBuilder/_stores/utils.js @@ -672,3 +672,34 @@ export function convertAllKeysToSnakeCase(o) { // return { suggestionList, hintsMap, resolvedRefs }; // } + +export const hasArrayNotation = (property) => { + // Regular expression to match array notation pattern + const arrayPattern = /\[\d+\]/; + return arrayPattern.test(property); +}; + +export const parsePropertyPath = (property) => { + // Split the property path into segments + const segments = property.split('.'); + const result = []; + + for (const segment of segments) { + // Check if segment contains array notation + if (hasArrayNotation(segment)) { + // Extract the property name and array index + const [name, ...rest] = segment.split('['); + if (name) result.push(name); + + // Extract and clean up array indices + for (const item of rest) { + const index = parseInt(item.replace(']', '')); + result.push(index); + } + } else { + result.push(segment); + } + } + + return result; +}; From 49349e2d5af6862b27fc6a30a5b8cb4ef0be4b93 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:32:11 +0530 Subject: [PATCH 17/92] Fixed multiselect & dropdown issues --- .../Components/QueryManagerBody.jsx | 1 + .../_stores/slices/componentsSlice.js | 42 +++++++++++++++---- .../Components/DropdownV2/DropdownV2.jsx | 20 +++++---- .../MultiselectV2/MultiselectV2.jsx | 39 +++++++++++------ server/src/helpers/components.helper.ts | 8 ++-- server/src/services/components.service.ts | 10 ++--- 6 files changed, 84 insertions(+), 36 deletions(-) diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx index c4c820ff69..2a7be4a37d 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerBody.jsx @@ -311,6 +311,7 @@ export const QueryManagerBody = ({ darkMode, options, setOptions, activeTab }) = ); }; + // if (selectedQueryId !== selectedQuery?.id) return; const hasPermissions = selectedDataSource?.scope === 'global' && selectedDataSource?.type !== DATA_SOURCE_TYPE.SAMPLE ? canUpdateDataSource(selectedQuery?.data_source_id) || diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index e5b10d2120..63f7e4cb4a 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -600,27 +600,47 @@ export const createComponentsSlice = (set, get) => ({ const updatedPropertyValue = cloneDeep(value); if (Array.isArray(value)) { value.forEach((val, index) => { - Object.entries(val).forEach(([key, keyValue]) => { - const propertyWithArrayValue = `${property}[${index}].${key}`; - const keys = [key]; - if (keyValue?.value) { - keys.push('value'); - } + //This code assumes that the array always consists of objects the else condition is to handle the case when the value is an array of strings/numbers + if (typeof val === 'object') { + Object.entries(val).forEach(([key, keyValue]) => { + const propertyWithArrayValue = `${property}[${index}].${key}`; + const keys = [key]; + if (keyValue?.value) { + keys.push('value'); + } + const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues( + componentId, + paramType, + propertyWithArrayValue, + keyValue?.value ?? keyValue, + component, + resolvedComponentValues, + updatePassedValue, + moduleId + ); + lodashSet(updatedPropertyValue, [index, ...keys], updatedValue); + if (allRefs.length) { + generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + } + }); + } else { + const propertyWithArrayValue = `${property}[${index}]`; const { allRefs, unResolvedValue, updatedValue } = updateResolvedValues( componentId, paramType, propertyWithArrayValue, - keyValue?.value ?? keyValue, + val, component, resolvedComponentValues, updatePassedValue, moduleId ); - lodashSet(updatedPropertyValue, [index, ...keys], updatedValue); + updatedPropertyValue[index] = updatedValue; + console.log('updatedPropertyValue', updatedPropertyValue); if (allRefs.length) { generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); } - }); + } }); return { updatedValue: updatedPropertyValue }; } else { @@ -1156,6 +1176,10 @@ export const createComponentsSlice = (set, get) => ({ true, moduleId ); + resolvedComponent[componentId][paramType][property] = resolvedComponent[componentId][paramType][property].slice( + 0, + value.length + ); setResolvedComponent(componentId, resolvedComponent[componentId], moduleId); // If the value is not changed, return diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index 1acddf4700..8b554ec3c2 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -90,7 +90,6 @@ export const DropdownV2 = ({ } = styles; const isInitialRender = useRef(true); const [currentValue, setCurrentValue] = useState(() => (advanced ? findDefaultItem(schema) : value)); - const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const isMandatory = validation?.mandatory ?? false; const options = properties?.options; const [validationStatus, setValidationStatus] = useState(validate(currentValue)); @@ -115,12 +114,12 @@ export const DropdownV2 = ({ let _options = advanced ? schema : options; if (Array.isArray(_options)) { let _selectOptions = _options - .filter((data) => getResolvedValue(advanced ? data?.visible : data?.visible?.value) ?? true) + .filter((data) => data?.visible ?? true) .map((data) => ({ ...data, - label: String(getResolvedValue(data?.label)), - value: getResolvedValue(data?.value), - isDisabled: getResolvedValue(advanced ? data?.disable : data?.disable?.value) ?? false, + label: data?.label, + value: data?.value, + isDisabled: data?.disable ?? false, })); return _selectOptions; @@ -177,9 +176,16 @@ export const DropdownV2 = ({ useEffect(() => { if (advanced) { setInputValue(findDefaultItem(schema)); - } else setInputValue(value); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [advanced, value, JSON.stringify(schema)]); + }, [advanced, JSON.stringify(schema)]); + + useEffect(() => { + if (!advanced) { + setInputValue(value); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [advanced, value]); useEffect(() => { document.addEventListener('mousedown', handleOutsideClick); diff --git a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx index 960955de57..2efe2d29b5 100644 --- a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx +++ b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx @@ -72,7 +72,6 @@ export const MultiselectV2 = ({ const valueContainerRef = React.useRef(null); const [visibility, setVisibility] = useState(properties.visibility); const [isMultiSelectLoading, setIsMultiSelectLoading] = useState(multiSelectLoadingState); - const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const [isMultiSelectDisabled, setIsMultiSelectDisabled] = useState(disabledState); const [isSelectAllSelected, setIsSelectAllSelected] = useState(false); const [searchInputValue, setSearchInputValue] = useState(''); @@ -91,12 +90,12 @@ export const MultiselectV2 = ({ const _options = advanced ? schema : options; let _selectOptions = Array.isArray(_options) ? _options - .filter((data) => getResolvedValue(advanced ? data?.visible : data?.visible?.value) ?? true) + .filter((data) => data?.visible ?? true) .map((data) => ({ ...data, - label: getResolvedValue(data?.label), - value: getResolvedValue(data?.value), - isDisabled: getResolvedValue(advanced ? data?.disable : data?.disable?.value) ?? false, + label: data?.label, + value: data?.value, + isDisabled: data?.disable ?? false, })) : []; return _selectOptions; @@ -129,9 +128,10 @@ export const MultiselectV2 = ({ } const onChangeHandler = (items, action) => { setInputValue(items); - if (action.action === 'select-option') { - fireEvent('onSelect'); - } + fireEvent('onSelect'); + // if (action.action === 'select-option') { + // fireEvent('onSelect'); + // } }; useEffect(() => { @@ -141,10 +141,20 @@ export const MultiselectV2 = ({ }, [selectOptions]); useEffect(() => { - let foundItem = findDefaultItem(values, advanced, true); - setInputValue(foundItem); + if (advanced) { + let foundItem = findDefaultItem(values, advanced, true); + setInputValue(foundItem); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [advanced, JSON.stringify(schema), JSON.stringify(values)]); + }, [advanced, JSON.stringify(values), JSON.stringify(schema)]); + + useEffect(() => { + if (!advanced) { + let foundItem = findDefaultItem(values, advanced, true); + setInputValue(foundItem); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [advanced, JSON.stringify(values)]); useEffect(() => { if (isInitialRender.current) return; @@ -487,7 +497,12 @@ export const MultiselectV2 = ({ containerRef={valueContainerRef} showAllOption={showAllOption} isSelectAllSelected={isSelectAllSelected} - setIsSelectAllSelected={setIsSelectAllSelected} + setIsSelectAllSelected={(value) => { + setIsSelectAllSelected(value); + if (!value) { + fireEvent('onSelect'); + } + }} setSelected={setInputValue} iconColor={iconColor} optionsLoadingState={optionsLoadingState && advanced} diff --git a/server/src/helpers/components.helper.ts b/server/src/helpers/components.helper.ts index a9aea3403e..f59eed972c 100644 --- a/server/src/helpers/components.helper.ts +++ b/server/src/helpers/components.helper.ts @@ -36,11 +36,13 @@ export const buildComponentMetaDefinition = (components = {}) => { componentMeta.definition.properties, currentComponentData?.component?.definition?.properties, (objValue, srcValue) => { - if ( - ['Table', 'DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) && + if (['Table'].includes(currentComponentData?.component?.component) && isArray(objValue)) { + return srcValue; + } else if ( + ['DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) && isArray(objValue) ) { - return srcValue; + return isArray(srcValue) ? srcValue : Object.values(srcValue); } } ), diff --git a/server/src/services/components.service.ts b/server/src/services/components.service.ts index cbc728a105..1d436a6e72 100644 --- a/server/src/services/components.service.ts +++ b/server/src/services/components.service.ts @@ -97,13 +97,13 @@ export class ComponentsService { componentData[column === 'others' ? 'displayPreferences' : column], updatedDefinition[column], (objValue, srcValue) => { - if ( - (componentData.type === 'Table' || - componentData.type === 'DropdownV2' || - componentData.type === 'MultiselectV2') && + if (componentData.type === 'Table' && _.isArray(objValue)) { + return srcValue; + } else if ( + (componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') && _.isArray(objValue) ) { - return srcValue; + return _.isArray(srcValue) ? srcValue : Object.values(srcValue); } } ); From 6a4eab9ec6b6ddcf081dcfc6c688a56e76d0e98d Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:31:06 +0530 Subject: [PATCH 18/92] Arrow key canvas movement fix with auto-alignment popover disappearing fix (#2600) --- .../AppCanvas/AutoComputeMobileLayoutAlert.jsx | 1 + frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 13 +++++++------ frontend/src/AppBuilder/_stores/slices/gridSlice.js | 10 +++++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AutoComputeMobileLayoutAlert.jsx b/frontend/src/AppBuilder/AppCanvas/AutoComputeMobileLayoutAlert.jsx index a36209e525..1124c326ad 100644 --- a/frontend/src/AppBuilder/AppCanvas/AutoComputeMobileLayoutAlert.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AutoComputeMobileLayoutAlert.jsx @@ -77,6 +77,7 @@ export default function AutoComputeMobileLayoutAlert({ currentLayout, darkMode } padding: 'var(--7, 16px)', background: 'var(--base)', margin: '10px', + zIndex: '1', }} className="d-flex flex-row" > diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 985b62eb89..f5603de836 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -44,7 +44,8 @@ export default function Grid({ gridWidth, currentLayout }) { const isGroupHandleHoverd = useIsGroupHandleHoverd(); const openModalWidgetId = useOpenModalWidgetId(); const moveableRef = useRef(null); - const [triggerCanvasUpdater, setTriggerCanvasUpdater] = useState(false); + const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow); + const toggleCanvasUpdater = useStore((state) => state.toggleCanvasUpdater, shallow); const groupResizeDataRef = useRef([]); const isDraggingRef = useRef(false); const canvasWidth = NO_OF_GRIDS * gridWidth; @@ -347,7 +348,7 @@ export default function Grid({ gridWidth, currentLayout }) { return layouts; }, {}); setComponentLayout(updatedLayouts, newParent, undefined, { updateParent: true }); - setTriggerCanvasUpdater((prev) => !prev); + toggleCanvasUpdater(); }, // eslint-disable-next-line react-hooks/exhaustive-deps [boxList, currentLayout, gridWidth] @@ -488,7 +489,7 @@ export default function Grid({ gridWidth, currentLayout }) { console.error('ResizeEnd error ->', error); } useGridStore.getState().actions.setDragTarget(); - setTriggerCanvasUpdater((prev) => !prev); + toggleCanvasUpdater(); }} onResizeStart={(e) => { if (!isComponentVisible(e.target.id)) { @@ -575,7 +576,7 @@ export default function Grid({ gridWidth, currentLayout }) { } catch (error) { console.error('Error resizing group', error); } - setTriggerCanvasUpdater((prev) => !prev); + toggleCanvasUpdater(); }} checkInput onDragStart={(e) => { @@ -722,7 +723,7 @@ export default function Grid({ gridWidth, currentLayout }) { element.classList.add('hide-grid'); }); document.getElementById('real-canvas')?.classList.remove('show-grid'); - setTriggerCanvasUpdater((prev) => !prev); + toggleCanvasUpdater(); }} onDrag={(e) => { // Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again @@ -857,7 +858,7 @@ export default function Grid({ gridWidth, currentLayout }) { } catch (error) { console.error('Error dragging group', error); } - setTriggerCanvasUpdater((prev) => !prev); + toggleCanvasUpdater(); }} // throttleDrag={1} // edgeDraggable={false} diff --git a/frontend/src/AppBuilder/_stores/slices/gridSlice.js b/frontend/src/AppBuilder/_stores/slices/gridSlice.js index 3a6e3146d5..69e5560497 100644 --- a/frontend/src/AppBuilder/_stores/slices/gridSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/gridSlice.js @@ -1,7 +1,9 @@ import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import { debounce } from 'lodash'; const initialState = { hoveredComponentForGrid: '', + triggerCanvasUpdater: false, }; export const createGridSlice = (set, get) => ({ @@ -9,8 +11,13 @@ export const createGridSlice = (set, get) => ({ setHoveredComponentForGrid: (id) => set(() => ({ hoveredComponentForGrid: id }), false, { type: 'setHoveredComponentForGrid', id }), getHoveredComponentForGrid: () => get().hoveredComponentForGrid, + toggleCanvasUpdater: () => + set((state) => ({ triggerCanvasUpdater: !state.triggerCanvasUpdater }), false, { type: 'toggleCanvasUpdater' }), + debouncedToggleCanvasUpdater: debounce(() => { + get().toggleCanvasUpdater(); + }, 200), moveComponentPosition: (direction) => { - const { setComponentLayout, currentLayout, getSelectedComponentsDefinition } = get(); + const { setComponentLayout, currentLayout, getSelectedComponentsDefinition, debouncedToggleCanvasUpdater } = get(); let layouts = {}; const selectedComponents = getSelectedComponentsDefinition(); selectedComponents.forEach((selectedComponent) => { @@ -53,5 +60,6 @@ export const createGridSlice = (set, get) => ({ }; }); setComponentLayout(layouts); + debouncedToggleCanvasUpdater(); }, }); From 1168ee940913406c11abb0f03c8b5b8f1bf9bead Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 22 Oct 2024 14:54:59 +0530 Subject: [PATCH 19/92] get exposed variables from form.children is element is a form child (#2489) --- .../AppBuilder/_stores/slices/resolvedSlice.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index c83f8408ce..6109176401 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -394,7 +394,22 @@ export const createResolvedSlice = (set, get) => ({ return get().resolvedStore?.modules?.[moduleId]?.components?.[componentId]; }, getExposedValueOfComponent: (componentId, moduleId = 'canvas') => { - return get().resolvedStore.modules[moduleId].exposedValues.components[componentId] || {}; + try { + const components = get().getCurrentPageComponents(); + const { + component: { parent: parentId, name: componentName }, + } = components[componentId]; + if (parentId) { + // if parent is form get exposed values from children + const { component: parentComopnent } = components[parentId]; + if (parentComopnent.component === 'Form') { + return get().resolvedStore.modules[moduleId].exposedValues.components[parentId].children[componentName] || {}; + } + } + return get().resolvedStore.modules[moduleId].exposedValues.components[componentId] || {}; + } catch (error) { + return {}; + } }, getAllExposedValues: (moduleId = 'canvas') => { return get().resolvedStore.modules[moduleId].exposedValues; From e9828a9b55bb44b84d662dadf16ccfb1a23d36a5 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Sat, 9 Nov 2024 13:30:46 +0530 Subject: [PATCH 20/92] Fixed modal not opening when inside a tab via show modal csa (#2603) --- frontend/src/AppBuilder/_stores/slices/resolvedSlice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 6109176401..4c29226864 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -401,8 +401,8 @@ export const createResolvedSlice = (set, get) => ({ } = components[componentId]; if (parentId) { // if parent is form get exposed values from children - const { component: parentComopnent } = components[parentId]; - if (parentComopnent.component === 'Form') { + const { component: parentComopnent } = components?.[parentId] || {}; + if (parentComopnent?.component === 'Form') { return get().resolvedStore.modules[moduleId].exposedValues.components[parentId].children[componentName] || {}; } } From e58180262feefcd1c14cc5edf53d93e7c6922725 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:03:54 +0530 Subject: [PATCH 21/92] Fixed resolving array of objects overlap issue (#2613) --- frontend/src/AppBuilder/_stores/slices/componentsSlice.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 63f7e4cb4a..c8a6a09aed 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1166,6 +1166,8 @@ export const createComponentsSlice = (set, get) => ({ if (Array.isArray(oldValue?.value)) { const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) }; + resolvedComponent[componentId][paramType][property] = []; + const { updatedValue } = checkValueAndResolve( componentId, paramType, @@ -1176,10 +1178,6 @@ export const createComponentsSlice = (set, get) => ({ true, moduleId ); - resolvedComponent[componentId][paramType][property] = resolvedComponent[componentId][paramType][property].slice( - 0, - value.length - ); setResolvedComponent(componentId, resolvedComponent[componentId], moduleId); // If the value is not changed, return From 2f765e12436d5a4007239c882f526cf8db54c42b Mon Sep 17 00:00:00 2001 From: Shaurya Sharma <79473274+shaurya-sharma064@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:45:58 +0530 Subject: [PATCH 22/92] Fixed dropdown not opening when clicked on input (#2618) * Fixed dropdown not opening when clicked on label * onFocus event fix --- .../Components/DropdownV2/DropdownV2.jsx | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index 8b554ec3c2..da1c28afb3 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -95,11 +95,12 @@ export const DropdownV2 = ({ const [validationStatus, setValidationStatus] = useState(validate(currentValue)); const { isValid, validationError } = validationStatus; const ref = React.useRef(null); + const dropdownRef = React.useRef(null); const [visibility, setVisibility] = useState(properties.visibility); const [isDropdownLoading, setIsDropdownLoading] = useState(dropdownLoadingState); const [isDropdownDisabled, setIsDropdownDisabled] = useState(disabledState); - const [isFocused, setIsFocused] = useState(false); const [searchInputValue, setSearchInputValue] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const _height = padding === 'default' ? `${height}px` : `${height + 4}px`; const labelRef = useRef(); function findDefaultItem(schema) { @@ -156,9 +157,14 @@ export const DropdownV2 = ({ const handleOutsideClick = (e) => { let menu = ref.current.querySelector('.select__menu'); if (!ref.current.contains(e.target) || !menu || !menu.contains(e.target)) { - setIsFocused(false); setSearchInputValue(''); } + if (dropdownRef.current && !dropdownRef.current?.contains(e.target) && !menu && !menu?.contains(e.target)) { + if (isDropdownOpen) { + fireEvent('onBlur'); + } + setIsDropdownOpen(false); + } }; const setInputValue = (value) => { @@ -192,7 +198,7 @@ export const DropdownV2 = ({ return () => { document.removeEventListener('mousedown', handleOutsideClick); }; - }, []); + }, [isDropdownOpen]); useEffect(() => { if (visibility !== properties.visibility) setVisibility(properties.visibility); @@ -407,6 +413,7 @@ export const DropdownV2 = ({ return ( <>
-
+
{ + if (!isDropdownDisabled) { + fireEvent('onFocus'); + setIsDropdownOpen((prev) => !prev); + } + }} + ref={ref} + >