@@ -76,6 +93,9 @@ function move_fields_to_column() {
>
+
@@ -107,12 +127,30 @@ function move_fields_to_column() {
}
}
+ :deep(.form-control:read-only:focus) {
+ box-shadow: none;
+ }
+
:deep(.field-controls) {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.3rem;
+ .field-label {
+ display: flex;
+ align-items: center;
+ .reqd-asterisk {
+ margin-left: 3px;
+ color: var(--red-400);
+ }
+ .help-icon {
+ margin-left: 3px;
+ color: var(--text-muted);
+ cursor: pointer;
+ }
+ }
+
.field-actions {
flex: none;
diff --git a/frappe/public/js/form_builder/components/FieldTypes.vue b/frappe/public/js/form_builder/components/FieldTypes.vue
index 04096e3015..2907614a51 100644
--- a/frappe/public/js/form_builder/components/FieldTypes.vue
+++ b/frappe/public/js/form_builder/components/FieldTypes.vue
@@ -3,15 +3,11 @@ import SearchBox from "./SearchBox.vue";
import draggable from "vuedraggable";
import { ref, computed } from "vue";
import { useStore } from "../store";
+import { clone_field } from "../utils";
let store = useStore();
let search_text = ref("");
-function clone_field(field) {
- field.df.name = frappe.utils.get_random(8);
- return JSON.parse(JSON.stringify(field));
-}
-
let fields = computed(() => {
let fields = frappe.model.all_fieldtypes
.filter(df => {
diff --git a/frappe/public/js/form_builder/components/FormBuilder.vue b/frappe/public/js/form_builder/components/FormBuilder.vue
index b4657066e8..c641643414 100644
--- a/frappe/public/js/form_builder/components/FormBuilder.vue
+++ b/frappe/public/js/form_builder/components/FormBuilder.vue
@@ -3,7 +3,7 @@ import Sidebar from "./Sidebar.vue";
import Tabs from "./Tabs.vue";
import { computed, onMounted, watch, ref } from "vue";
import { useStore } from "../store";
-import { onClickOutside } from "@vueuse/core";
+import { onClickOutside, useMagicKeys, whenever } from "@vueuse/core";
let store = useStore();
@@ -14,6 +14,14 @@ let should_render = computed(() => {
let container = ref(null);
onClickOutside(container, () => store.selected_field = null);
+// cmd/ctrl + s to save the form
+const { meta_s, ctrl_s } = useMagicKeys();
+whenever(() => meta_s.value || ctrl_s.value, () => {
+ if (store.dirty) {
+ store.save_changes();
+ }
+});
+
function setup_change_doctype_dialog() {
store.page.$title_area.on("click", () => {
let dialog = new frappe.ui.Dialog({
@@ -109,7 +117,7 @@ onMounted(() => {
font-size: var(--text-sm);
cursor: pointer;
- &:has(.drop-it-here) {
+ &:not(.hovered) {
position: relative;
background-color: transparent;
height: 60px;
@@ -169,10 +177,6 @@ onMounted(() => {
}
}
- .reqd::after {
- content: " *";
- color: var(--red-400);
- }
.description,
.time-zone {
font-size: var(--text-sm);
@@ -264,7 +268,7 @@ onMounted(() => {
}
}
- .form-main:not(:has(.tab-header)) :deep(.tab-contents) {
+ .form-main > :deep(div:first-child:not(.tab-header)) {
max-height: calc(100vh - 160px);
}
}
diff --git a/frappe/public/js/form_builder/components/controls/DataControl.vue b/frappe/public/js/form_builder/components/controls/DataControl.vue
index aec72925a5..2052912130 100644
--- a/frappe/public/js/form_builder/components/controls/DataControl.vue
+++ b/frappe/public/js/form_builder/components/controls/DataControl.vue
@@ -43,7 +43,7 @@ if (props.df.fieldtype === "Icon") {
type="text"
:style="{ height: df.fieldtype == 'Table MultiSelect' ? '42px' : '' }"
:placeholder="placeholder"
- disabled
+ readonly
/>
diff --git a/frappe/public/js/form_builder/components/controls/LinkControl.vue b/frappe/public/js/form_builder/components/controls/LinkControl.vue
index 1779f45d6f..ce58c9a81e 100644
--- a/frappe/public/js/form_builder/components/controls/LinkControl.vue
+++ b/frappe/public/js/form_builder/components/controls/LinkControl.vue
@@ -77,7 +77,7 @@ watch(
-