From 5e0ea1e98692d18fea340baf2ac6ad0a1a089364 Mon Sep 17 00:00:00 2001 From: Harsh Tandiya Date: Sat, 25 Apr 2026 21:57:06 +0530 Subject: [PATCH 1/5] feat(form-field): add Heading 1/2/3 fieldtypes to doctype and backend mapping Maps heading fieldtypes to Frappe HTML, generates h1/h2/h3 options content for the CustomField, and skips heading fields in server-side required validation. --- forms_pro/api/submission.py | 3 +++ .../doctype/form_field/form_field.json | 4 ++-- .../doctype/form_field/form_field.py | 23 ++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/forms_pro/api/submission.py b/forms_pro/api/submission.py index 7e18b82..73abfdb 100644 --- a/forms_pro/api/submission.py +++ b/forms_pro/api/submission.py @@ -8,6 +8,7 @@ from pydantic import BaseModel, Field, field_validator from forms_pro.forms_pro.doctype.form.form import Form +from forms_pro.forms_pro.doctype.form_field.form_field import _DISPLAY_ONLY_FIELDTYPES from forms_pro.utils.form_generator import SubmissionStatus @@ -85,6 +86,8 @@ def _validate_form_response(form: "Form", form_data: dict) -> None: errors: list[str] = [] for field in form.fields: + if field.fieldtype in _DISPLAY_ONLY_FIELDTYPES: + continue is_visible = not field.hidden is_required = bool(field.reqd) 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 d1dc0ab..02a9a11 100644 --- a/forms_pro/forms_pro/doctype/form_field/form_field.json +++ b/forms_pro/forms_pro/doctype/form_field/form_field.json @@ -37,7 +37,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\nCheckbox\nRating\nPhone\nTable\nMultiselect", + "options": "Attach\nData\nNumber\nEmail\nDate\nDate Time\nDate Range\nTime Picker\nPassword\nSelect\nSwitch\nTextarea\nText Editor\nLink\nCheckbox\nRating\nPhone\nTable\nMultiselect\nHeading 1\nHeading 2\nHeading 3", "reqd": 1 }, { @@ -81,7 +81,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2026-04-15 16:26:10.519579", + "modified": "2026-04-25 20:32:10.994784", "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 269b882..8bceff1 100644 --- a/forms_pro/forms_pro/doctype/form_field/form_field.py +++ b/forms_pro/forms_pro/doctype/form_field/form_field.py @@ -29,9 +29,15 @@ "Rating": {"fieldtype": "Rating"}, "Table": {"fieldtype": "Table"}, "Multiselect": {"fieldtype": "JSON"}, + "Heading 1": {"fieldtype": "HTML"}, + "Heading 2": {"fieldtype": "HTML"}, + "Heading 3": {"fieldtype": "HTML"}, } +_DISPLAY_ONLY_FIELDTYPES = {"Heading 1", "Heading 2", "Heading 3"} + + class FormField(Document): # begin: auto-generated types # This code is auto-generated. Do not modify anything in this block. @@ -65,6 +71,9 @@ class FormField(Document): "Phone", "Table", "Multiselect", + "Heading 1", + "Heading 2", + "Heading 3", ] hidden: DF.Check label: DF.Data @@ -83,7 +92,19 @@ def to_frappe_field(self) -> dict: "fieldtype": mapping.get("fieldtype", self.fieldtype), "label": self.label, "reqd": self.reqd, - "options": mapping.get("options", self.options), + "options": mapping.get("options", self.get_options()), "description": self.description, "default": self.default, } + + def get_options(self) -> str: + if self.fieldtype in _DISPLAY_ONLY_FIELDTYPES: + HEADING_MAP = { + "Heading 1": "h1", + "Heading 2": "h2", + "Heading 3": "h3", + } + tag = HEADING_MAP.get(self.fieldtype, "h2") + return f"<{tag}>{self.label}" + + return self.options From ef02dd9b0ada7d92d9755ac7763be43b9922664e Mon Sep 17 00:00:00 2001 From: Harsh Tandiya Date: Sat, 25 Apr 2026 21:57:15 +0530 Subject: [PATCH 2/5] feat(heading): wire up Heading 1/2/3 field types across the frontend Adds heading layout type, Heading component with h2/h3/h4 tag rendering, FieldRenderer branch for edit/view modes, isHeading util, and submission display handling. --- .../src/components/builder/FieldRenderer.vue | 9 +++ frontend/src/components/fields/Heading.vue | 57 +++++++++++++++++++ .../form/submissions/SubmissionFieldValue.vue | 9 ++- frontend/src/config/fieldTypes.ts | 35 +++++++++++- .../src/types/FormsPro/form_field.types.ts | 3 + frontend/src/utils/form_fields.ts | 8 +++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/fields/Heading.vue diff --git a/frontend/src/components/builder/FieldRenderer.vue b/frontend/src/components/builder/FieldRenderer.vue index 2a50def..91ca907 100644 --- a/frontend/src/components/builder/FieldRenderer.vue +++ b/frontend/src/components/builder/FieldRenderer.vue @@ -83,6 +83,15 @@ const { options: selectOptions } = useFieldOptions(fieldData); /> + +
+ +
+
+import { Fieldtype } from "@/types/FormsPro/form_field.types"; +import { computed } from "vue"; + +const props = defineProps<{ + field: { + label: string; + fieldtype: Fieldtype; + }; + inEditMode: boolean; +}>(); + +const emit = defineEmits<{ + "update:label": [value: string]; +}>(); + +const headingClasses: Record = { + [Fieldtype.HEADING_1]: "text-xl font-bold", + [Fieldtype.HEADING_2]: "text-lg font-semibold", + [Fieldtype.HEADING_3]: "text-base font-semibold", +}; + +type HeadingFieldtype = Fieldtype.HEADING_1 | Fieldtype.HEADING_2 | Fieldtype.HEADING_3; +type HeadingTag = "h2" | "h3" | "h4"; + +const headingTypeToTag: Record = { + [Fieldtype.HEADING_1]: "h2", + [Fieldtype.HEADING_2]: "h3", + [Fieldtype.HEADING_3]: "h4", +}; + +const headingTag = computed( + () => headingTypeToTag[props.field.fieldtype as HeadingFieldtype] ?? "h2" +); + +const headingClass = computed( + () => headingClasses[props.field.fieldtype as HeadingFieldtype] ?? "" +); + + + diff --git a/frontend/src/components/form/submissions/SubmissionFieldValue.vue b/frontend/src/components/form/submissions/SubmissionFieldValue.vue index c6754bb..f645b52 100644 --- a/frontend/src/components/form/submissions/SubmissionFieldValue.vue +++ b/frontend/src/components/form/submissions/SubmissionFieldValue.vue @@ -4,6 +4,7 @@ import { Fieldtype } from "@/types/formfield"; import { getFieldTypeDef } from "@/config/fieldTypes"; import { formatDate, formatDateTime, formatTime } from "@/utils/date"; import { computed } from "vue"; +import { isHeading } from "@/utils/form_fields"; const props = defineProps<{ fieldname: string; @@ -75,7 +76,7 @@ const classNames = computed(() =>