diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 4927f1e300..4a30ad68e0 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -835,6 +835,60 @@ export default class GridRow { : ""; add_class += ["Check"].indexOf(df.fieldtype) !== -1 ? " text-center" : ""; + let grid; + let grid_container; + + let inital_position_x = 0; + let start_x = 0; + let start_y = 0; + + let input_in_focus = false; + + let vertical = false; + let horizontal = false; + + // prevent random layout shifts caused by widgets and on click position elements inside view (UX). + function on_input_focus(el) { + input_in_focus = true; + + let container_width = grid_container.getBoundingClientRect().width; + let container_left = grid_container.getBoundingClientRect().left; + let grid_left = parseFloat(grid.style.left); + let element_left = el.offset().left; + let fieldtype = el.data("fieldtype"); + + let offset_right = container_width - (element_left + el.width()); + let offset_left = 0; + let element_screen_x = element_left - container_left; + let element_position_x = container_width - (element_left - container_left); + + if (["Date", "Time", "Datetime"].includes(fieldtype)) { + offset_left = element_position_x - 220; + } + if (["Link", "Dynamic Link"].includes(fieldtype)) { + offset_left = element_position_x - 250; + } + if (element_screen_x < 0) { + grid.style.left = `${grid_left - element_screen_x}px`; + } else if (offset_left < 0) { + grid.style.left = `${grid_left + offset_left}px`; + } else if (offset_right < 0) { + grid.style.left = `${grid_left + offset_right}px`; + } + } + + // Delay date_picker widget to prevent temparary layout shift (UX). + function handle_date_picker() { + let date_time_picker = document.querySelectorAll(".datepicker.active")[0]; + + date_time_picker.classList.remove("active"); + date_time_picker.style.width = "220px"; + + setTimeout(() => { + date_time_picker.classList.add("active"); + }, 600); + } + var $col = $( '
' ) @@ -842,15 +896,68 @@ export default class GridRow { .attr("data-fieldtype", df.fieldtype) .data("df", df) .appendTo(this.row) - .on("click", function () { - if (frappe.ui.form.editable_row === me) { - return; + // initialize grid for horizontal scroll on mobile devices. + .on("touchstart", function (event) { + grid_container = $(event.currentTarget).closest(".form-grid-container")[0]; + grid = $(event.currentTarget).closest(".form-grid")[0]; + + grid.style.position != "relative" && $(grid).css("position", "relative"); + !grid.style.left && $(grid).css("left", 0); + + start_x = event.touches[0].clientX; + start_y = event.touches[0].clientY; + + inital_position_x = -parseFloat(grid.style.left || 0) + start_x; + }) + // calculate X and Y movement based on touch events. + .on("touchmove", function (event) { + if (input_in_focus) return; + + let moved_x; + let moved_y; + + if (!horizontal && !vertical) { + moved_x = Math.abs(start_x - event.touches[0].clientX); + moved_y = Math.abs(start_y - event.touches[0].clientY); + } + + if (!vertical && moved_x > 16) { + horizontal = true; + } else if (!horizontal && moved_y > 16) { + vertical = true; + } + if (horizontal) { + event.preventDefault(); + + let grid_start = inital_position_x - event.touches[0].clientX; + let grid_end = grid.clientWidth - grid_container.clientWidth + 2; + + if (grid_start < 0) { + grid_start = 0; + } else if (grid_start > grid_end) { + grid_start = grid_end; + } + grid.style.left = `-${grid_start}px`; + } + }) + .on("touchend", function () { + vertical = false; + horizontal = false; + }) + .on("click", function () { + if (frappe.ui.form.editable_row !== me) { + var out = me.toggle_editable_row(); } - var out = me.toggle_editable_row(); var col = this; - setTimeout(function () { - $(col).find('input[type="Text"]:first').focus(); - }, 500); + let first_input_field = $(col).find('input[type="Text"]:first'); + + first_input_field.length && on_input_focus(first_input_field); + + first_input_field.trigger("focus"); + first_input_field.one("blur", () => (input_in_focus = false)); + + first_input_field.data("fieldtype") == "Date" && handle_date_picker(); + return out; }); @@ -1151,6 +1258,7 @@ export default class GridRow { show_form() { if (frappe.utils.is_xs()) { $(this.grid.form_grid).css("min-width", "0"); + $(this.grid.form_grid).css("position", "unset"); } if (!this.grid_form) { this.grid_form = new GridRowForm({ @@ -1191,7 +1299,8 @@ export default class GridRow { } hide_form() { if (frappe.utils.is_xs()) { - $(this.grid.form_grid).css("min-width", "1000px"); + $(this.grid.form_grid).css("min-width", "738px"); + $(this.grid.form_grid).css("position", "relative"); } frappe.dom.unfreeze(); this.row.toggle(true); diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 3bee40d2f2..ac15894670 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -268,8 +268,8 @@ .editable-row .frappe-control { padding-top: 0px !important; padding-bottom: 0px !important; - margin-left: -5px !important; - margin-right: -5px !important; + margin-left: -1px !important; + margin-right: -1px !important; } } @@ -486,10 +486,10 @@ @media (max-width: map-get($grid-breakpoints, "md")) { .form-grid-container { - overflow-x: scroll; + overflow-x: clip; .form-grid { - min-width: 1000px; + min-width: 738px; } } @@ -500,6 +500,16 @@ } } +@media (min-width: map-get($grid-breakpoints, "md")) { + .form-grid-container { + overflow-x: unset!important; + + .form-grid { + position: unset!important; + } + } +} + @media (max-width: map-get($grid-breakpoints, "sm")) { .form-in-grid .form-section .form-column { padding-left: 0 !important;