From ec0912355e03c6c15a4ec4fb14a6ea96ec16fd6e Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 16 Oct 2022 23:15:13 +0530 Subject: [PATCH] fix(UI): child table custom horizontal scroll (cherry picked from commit 60d6cb1040d093f2bc6d53bc13783732177ce051) --- frappe/public/js/frappe/form/grid_row.js | 120 +++++++++++++++++++++-- frappe/public/scss/common/grid.scss | 20 +++- 2 files changed, 131 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 4927f1e300..4c021a8af4 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -835,6 +835,18 @@ export default class GridRow { : ""; add_class += ["Check"].indexOf(df.fieldtype) !== -1 ? " text-center" : ""; + let grid; + let gridContainer; + + let initalPositionX = 0; + let startX = 0; + let startY = 0; + + let inputInFocus = false; + + let vertical = false; + let horizontal = false; + var $col = $( '
' ) @@ -842,15 +854,107 @@ export default class GridRow { .attr("data-fieldtype", df.fieldtype) .data("df", df) .appendTo(this.row) + // initialize grid for horizontal scroll on mobile devices. + .on("touchstart", function (event) { + gridContainer = $(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); + + startX = event.touches[0].clientX; + startY = event.touches[0].clientY; + + initalPositionX = -parseFloat(grid.style.left || 0) + startX; + }) + // calculate X and Y movement based on touch events. + .on("touchmove", function (event) { + if (inputInFocus) return; + + let movedX; + let movedY; + + if (!horizontal && !vertical) { + movedX = Math.abs(startX - event.touches[0].clientX); + movedY = Math.abs(startY - event.touches[0].clientY); + } + + if (!vertical && movedX > 16) { + horizontal = true; + } else if (!horizontal && movedY > 16) { + vertical = true; + } + if (horizontal) { + event.preventDefault(); + + let gridStart = initalPositionX - event.touches[0].clientX; + let gridEnd = grid.clientWidth - gridContainer.clientWidth; + + if (gridStart < 0) { + gridStart = 0; + } else if (gridStart > gridEnd) { + gridStart = gridEnd; + } + grid.style.left = `-${gridStart}px`; + } + }) + .on("touchend", function () { + vertical = false; + horizontal = false; + }) .on("click", function () { - if (frappe.ui.form.editable_row === me) { - return; + 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 firstInputField = $(col).find('input[type="Text"]:first'); + // prevent random layout shifts caused by widgets and on click position elements inside view (UX). + function onInputFocus(el) { + inputInFocus = true; + + let containerWidth = gridContainer.getBoundingClientRect().width; + let containerLeft = gridContainer.getBoundingClientRect().left; + let gridLeft = parseFloat(grid.style.left); + let elementLeft = el.offset().left; + let fieldType = el.data("fieldtype"); + + let offsetRight = containerWidth - (elementLeft + el.width()); + let offsetLeft = 0; + let elementScreenX = elementLeft - containerLeft; + let elementPositionX = containerWidth - (elementLeft - containerLeft); + + if (["Date", "Time", "Datetime"].includes(fieldType)) { + offsetLeft = elementPositionX - 220; + } + if (["Link", "Dynamic Link"].includes(fieldType)) { + offsetLeft = elementPositionX - 250; + } + if (elementScreenX < 0) { + grid.style.left = `${gridLeft - elementScreenX}px`; + } else if (offsetLeft < 0) { + grid.style.left = `${gridLeft + offsetLeft}px`; + } else if (offsetRight < 0) { + grid.style.left = `${gridLeft + offsetRight}px`; + } + } + + firstInputField.length && onInputFocus(firstInputField); + + firstInputField.focus(); + firstInputField.one("blur", () => (inputInFocus = false)); + + // Delay datePicker widget to prevent temparary layout shift (UX). + if (firstInputField.data("fieldtype") == "Date") { + let dateTimePicker = document.querySelectorAll(".datepicker.active")[0]; + + dateTimePicker.classList.remove("active"); + + dateTimePicker.style.width = "220px"; + + setTimeout(() => { + dateTimePicker.classList.add("active"); + }, 600); + } return out; }); @@ -1151,6 +1255,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 +1296,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..f0cd91532e 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -486,10 +486,26 @@ @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; + } + } + + .form-column.col-sm-6 .form-grid { + .row-index { + display: block; + } + } +} + +@media (min-width: map-get($grid-breakpoints, "md")) { + .form-grid-container { + overflow-x: unset!important; + + .form-grid { + position: unset!important; } }