Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions forms_pro/forms_pro/doctype/form_field/form_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -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
},
{
Expand All @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion forms_pro/forms_pro/doctype/form_field/form_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +33,8 @@ class FormField(Document):
"Textarea",
"Text Editor",
"Link",
"Checkbox",
"Rating",
]
label: DF.Data
options: DF.SmallText | None
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/FieldEditorSidebar.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useEditForm } from "@/stores/editForm";
import FieldPropertiesForm from "@/components/builder/FieldPropertiesForm.vue";
import FieldPropertiesForm from "@/components/builder/field-editor/FieldPropertiesForm.vue";

const editFormStore = useEditForm();
</script>
Expand Down
84 changes: 81 additions & 3 deletions frontend/src/components/FormBuilderContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,54 @@ const editFormStore = useEditForm();
// Ref for the entire FormBuilderContent component
const fieldContentRef = ref<HTMLElement | null>(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
Expand All @@ -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);
}
});
Expand All @@ -36,6 +114,7 @@ onClickOutside(fieldContentRef, (event) => {
</div>
<div
v-if="editFormStore.formData"
ref="fieldContentRef"
class="bg-secondary min-h-[800px] max-w-screen-md w-full border rounded my-12 p-4 shadow-[0_0_10px_rgba(0,0,0,0.1)]"
>
<div class="flex flex-col gap-2">
Expand Down Expand Up @@ -69,7 +148,6 @@ onClickOutside(fieldContentRef, (event) => {
<draggableComponent :list="editFormStore.fields" item-key="idx" tag="div">
<template #item="{ element }">
<div
ref="fieldContentRef"
@click="editFormStore.selectField(element)"
:class="{ 'border-gray-400': editFormStore.selectedField === element }"
class="p-2 my-3 bg-gray-50 rounded border flex gap-2 relative transition-colors"
Expand Down
66 changes: 0 additions & 66 deletions frontend/src/components/builder/FieldPropertiesForm.vue

This file was deleted.

Loading