diff --git a/forms_pro/forms_pro/doctype/form_field/form_field.json b/forms_pro/forms_pro/doctype/form_field/form_field.json index 236861a..5f74562 100644 --- a/forms_pro/forms_pro/doctype/form_field/form_field.json +++ b/forms_pro/forms_pro/doctype/form_field/form_field.json @@ -6,14 +6,15 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "reqd", "label", + "fieldtype", "fieldname", "description", "column_break_yznn", - "fieldtype", - "reqd", "options", - "default" + "default", + "conditional_logic" ], "fields": [ { @@ -35,7 +36,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Attach\nData\nNumber\nEmail\nDate\nDate Time\nDate Range\nTime Picker\nPassword\nSelect\nSwitch\nTextarea\nText Editor\nLink", + "options": "Attach\nData\nNumber\nEmail\nDate\nDate Time\nDate Range\nTime Picker\nPassword\nSelect\nSwitch\nTextarea\nText Editor\nLink\nCheckbox\nRating", "reqd": 1 }, { @@ -62,13 +63,18 @@ "fieldname": "default", "fieldtype": "Small Text", "label": "Default" + }, + { + "fieldname": "conditional_logic", + "fieldtype": "Code", + "label": "Conditional Logic" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2026-01-09 14:57:39.192268", + "modified": "2026-01-14 21:40:40.060805", "modified_by": "Administrator", "module": "Forms Pro", "name": "Form Field", diff --git a/forms_pro/forms_pro/doctype/form_field/form_field.py b/forms_pro/forms_pro/doctype/form_field/form_field.py index 12f3caa..3c41811 100644 --- a/forms_pro/forms_pro/doctype/form_field/form_field.py +++ b/forms_pro/forms_pro/doctype/form_field/form_field.py @@ -14,6 +14,7 @@ class FormField(Document): if TYPE_CHECKING: from frappe.types import DF + conditional_logic: DF.Code | None default: DF.SmallText | None description: DF.SmallText | None fieldname: DF.Data @@ -32,6 +33,8 @@ class FormField(Document): "Textarea", "Text Editor", "Link", + "Checkbox", + "Rating", ] label: DF.Data options: DF.SmallText | None @@ -56,7 +59,7 @@ def to_frappe_field(self) -> dict: _fieldtype = "Data" elif self.fieldtype == "Time Picker": _fieldtype = "Time" - elif self.fieldtype == "Switch": + elif self.fieldtype == "Switch" or self.fieldtype == "Checkbox": _fieldtype = "Check" elif self.fieldtype == "Textarea": _fieldtype = "Text" diff --git a/frontend/src/components/FieldEditorSidebar.vue b/frontend/src/components/FieldEditorSidebar.vue index 9b34272..0192aa2 100644 --- a/frontend/src/components/FieldEditorSidebar.vue +++ b/frontend/src/components/FieldEditorSidebar.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/components/FormBuilderContent.vue b/frontend/src/components/FormBuilderContent.vue index d67a833..9ccf909 100644 --- a/frontend/src/components/FormBuilderContent.vue +++ b/frontend/src/components/FormBuilderContent.vue @@ -14,6 +14,54 @@ const editFormStore = useEditForm(); // Ref for the entire FormBuilderContent component const fieldContentRef = ref(null); +// Function to check if an element is a dropdown/popover (including portals) +const isDropdownOrPopover = (element: Element | null): boolean => { + if (!element) return false; + + // Walk up the DOM tree to check for dropdown indicators + let current: Element | null = element; + while (current && current !== document.body) { + // Check for Headless UI patterns + if ( + current.hasAttribute("role") && + (current.getAttribute("role") === "listbox" || + current.getAttribute("role") === "option" || + current.getAttribute("role") === "combobox") + ) { + return true; + } + + // Check for Headless UI data attributes + if (current.hasAttribute("data-headlessui-state") || current.id?.includes("headlessui")) { + return true; + } + + // Check for Radix UI patterns + if ( + current.hasAttribute("data-radix-popper-content-wrapper") || + current.id?.startsWith("radix") || + current.hasAttribute("data-radix") + ) { + return true; + } + + // Check for common dropdown classes + const classList = current.classList; + if ( + classList.contains("dropdown-menu") || + classList.contains("combobox-options") || + classList.contains("popover-content") || + current.hasAttribute("data-popover") + ) { + return true; + } + + current = current.parentElement; + } + + return false; +}; + // Set up outside click detection for the entire FormBuilderContent component onClickOutside(fieldContentRef, (event) => { // Check if the click is on any other form builder components @@ -24,8 +72,38 @@ onClickOutside(fieldContentRef, (event) => { target.closest(".form-builder-sidebar") || target.closest(".form-builder-header"); - // Only deselect if NOT clicking on other form builder components - if (!isFormBuilderComponent) { + // Check if the click is on a dropdown menu (which may be rendered in a portal) + // This handles Headless UI, Radix UI, and other common dropdown patterns + const isDropdownElement = isDropdownOrPopover(target); + + // Also check if there are any visible/open dropdowns in the DOM + // This catches dropdowns that might be open but the click target isn't directly on them + const hasOpenDropdown = !!( + document.querySelector('[role="listbox"]:not([hidden]):not([style*="display: none"])') || + document.querySelector('[role="combobox"][aria-expanded="true"]') || + document.querySelector('[data-headlessui-state="open"]') || + document.querySelector('[aria-expanded="true"][role="combobox"]') + ); + + // Check if the active element (focused element) is within the sidebar + // This helps catch cases where a dropdown is open and the user is interacting with it + const activeElement = document.activeElement; + const isActiveElementInSidebar = activeElement + ? !!( + activeElement.closest(".field-editor-sidebar") || + activeElement.closest('[data-form-builder-component="field-editor-sidebar"]') || + activeElement.closest('[data-form-builder-component="field-properties-form"]') + ) + : false; + + // Only deselect if NOT clicking on other form builder components or dropdowns + // Also don't deselect if there's an open dropdown or if the active element is in the sidebar + if ( + !isFormBuilderComponent && + !isDropdownElement && + !hasOpenDropdown && + !isActiveElementInSidebar + ) { editFormStore.selectField(null); } }); @@ -36,6 +114,7 @@ onClickOutside(fieldContentRef, (event) => {
@@ -69,7 +148,6 @@ onClickOutside(fieldContentRef, (event) => {