From 3a842767272b8b90ecc680235b6c858b89f41718 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Thu, 12 Dec 2024 07:07:58 -0500 Subject: [PATCH] [IMP] web_widget_product_label_section_and_note: Auto-resize textarea for multiline text. Before this change, when the text was large and multiline, the textarea did not resize, and the text was not displayed correctly. Now, the textarea adjusts according to the text. These changes are backported from V18. --- .../README.rst | 2 +- .../__manifest__.py | 1 + .../readme/ROADMAP.rst | 2 +- .../static/description/index.html | 2 +- ...roduct_label_section_and_note_field.esm.js | 7 ++ .../static/src/core/utils/autoresize.esm.js | 111 ++++++++++++++++++ .../utils/product_and_label_autoresize.esm.js | 41 +++++++ 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 web_widget_product_label_section_and_note/static/src/core/utils/autoresize.esm.js create mode 100644 web_widget_product_label_section_and_note/static/src/core/utils/product_and_label_autoresize.esm.js diff --git a/web_widget_product_label_section_and_note/README.rst b/web_widget_product_label_section_and_note/README.rst index 4d03bc616..0536eb1c7 100644 --- a/web_widget_product_label_section_and_note/README.rst +++ b/web_widget_product_label_section_and_note/README.rst @@ -57,7 +57,7 @@ Usage Known issues / Roadmap ====================== -- Add compatibility with `sale` module +- Add compatibility with `sale_product_configurator` module - Add compatibility with `purchase_product_matrix` module - When this module is installed, the PDF report will always display the column `name` as a combination of the `product code`, `product name`, and `description`. This behavior may differ from the expected behavior, where only the column `name` is displayed and can be customized independently. diff --git a/web_widget_product_label_section_and_note/__manifest__.py b/web_widget_product_label_section_and_note/__manifest__.py index 05eae540e..3f37cd489 100644 --- a/web_widget_product_label_section_and_note/__manifest__.py +++ b/web_widget_product_label_section_and_note/__manifest__.py @@ -13,6 +13,7 @@ ], "assets": { "web.assets_backend": [ + "web_widget_product_label_section_and_note/static/src/core/utils/**/*", "web_widget_product_label_section_and_note/static/src/components/**/*", ], }, diff --git a/web_widget_product_label_section_and_note/readme/ROADMAP.rst b/web_widget_product_label_section_and_note/readme/ROADMAP.rst index f534a99ea..8e3c1f4bf 100644 --- a/web_widget_product_label_section_and_note/readme/ROADMAP.rst +++ b/web_widget_product_label_section_and_note/readme/ROADMAP.rst @@ -1,3 +1,3 @@ -- Add compatibility with `sale` module +- Add compatibility with `sale_product_configurator` module - Add compatibility with `purchase_product_matrix` module - When this module is installed, the PDF report will always display the column `name` as a combination of the `product code`, `product name`, and `description`. This behavior may differ from the expected behavior, where only the column `name` is displayed and can be customized independently. \ No newline at end of file diff --git a/web_widget_product_label_section_and_note/static/description/index.html b/web_widget_product_label_section_and_note/static/description/index.html index 0555ba047..c8204d0e7 100644 --- a/web_widget_product_label_section_and_note/static/description/index.html +++ b/web_widget_product_label_section_and_note/static/description/index.html @@ -407,7 +407,7 @@ in a new module, it is necessary to create an inherited view to change the widge

Known issues / Roadmap

diff --git a/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js index 7fffe74df..c90dbe63d 100644 --- a/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js +++ b/web_widget_product_label_section_and_note/static/src/components/product_label_section_and_note_field/product_label_section_and_note_field.esm.js @@ -20,6 +20,7 @@ import {X2ManyField} from "@web/views/fields/x2many/x2many_field"; import {_t} from "@web/core/l10n/translation"; import {getActiveHotkey} from "@web/core/hotkeys/hotkey_service"; import {registry} from "@web/core/registry"; +import {useProductAndLabelAutoresize} from "../../core/utils/product_and_label_autoresize.esm"; export class ProductLabelSectionAndNoteListRender extends SectionAndNoteListRenderer { setup() { @@ -138,7 +139,13 @@ export class ProductLabelSectionAndNoteField extends Many2OneField { value: this.props.record.columnIsProductAndLabel, }); this.labelNode = useRef("labelNodeRef"); + useProductAndLabelAutoresize(this.labelNode, { + targetParentName: this.props.name, + }); this.productNode = useRef("productNodeRef"); + useProductAndLabelAutoresize(this.productNode, { + targetParentName: this.props.name, + }); useEffect( () => { diff --git a/web_widget_product_label_section_and_note/static/src/core/utils/autoresize.esm.js b/web_widget_product_label_section_and_note/static/src/core/utils/autoresize.esm.js new file mode 100644 index 000000000..4136d43d2 --- /dev/null +++ b/web_widget_product_label_section_and_note/static/src/core/utils/autoresize.esm.js @@ -0,0 +1,111 @@ +/** @odoo-module **/ +/* Copyright Odoo S.A. + * Copyright 2024 Tecnativa - Carlos Lopez + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +import {useEffect} from "@odoo/owl"; +import {browser} from "@web/core/browser/browser"; + +function resizeInput(input) { + // This mesures the maximum width of the input which can get from the flex layout. + input.style.width = "100%"; + const maxWidth = input.clientWidth; + // Somehow Safari 16 computes input sizes incorrectly. This is fixed in Safari 17 + const isSafari16 = /Version\/16.+Safari/i.test(browser.navigator.userAgent); + // Minimum width of the input + input.style.width = "10px"; + if (input.value === "" && input.placeholder !== "") { + input.style.width = "auto"; + return; + } + if (input.scrollWidth + 5 + (isSafari16 ? 8 : 0) > maxWidth) { + input.style.width = "100%"; + return; + } + input.style.width = input.scrollWidth + 5 + (isSafari16 ? 8 : 0) + "px"; +} + +export function resizeTextArea(textarea, options = {}) { + const minimumHeight = options.minimumHeight || 0; + let heightOffset = 0; + const style = window.getComputedStyle(textarea); + if (style.boxSizing === "border-box") { + const paddingHeight = + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom); + const borderHeight = + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth); + heightOffset = borderHeight + paddingHeight; + } + const previousStyle = { + borderTopWidth: style.borderTopWidth, + borderBottomWidth: style.borderBottomWidth, + padding: style.padding, + }; + Object.assign(textarea.style, { + height: "auto", + borderTopWidth: 0, + borderBottomWidth: 0, + paddingTop: 0, + paddingRight: style.paddingRight, + paddingBottom: 0, + paddingLeft: style.paddingLeft, + }); + textarea.style.height = "auto"; + const height = Math.max(minimumHeight, textarea.scrollHeight + heightOffset); + Object.assign(textarea.style, previousStyle, {height: `${height}px`}); + textarea.parentElement.style.height = `${height}px`; +} + +/** + * This is used on text inputs or textareas to automatically resize it based on its + * content each time it is updated. It takes the reference of the element as + * parameter and some options. Do note that it may introduce mild performance issues + * since it will force a reflow of the layout each time the element is updated. + * Do also note that it only works with textareas that are nested as only child + * of some parent div (like in the text_field component). + * + * @param {Ref} ref + */ +export function useAutoresize(ref, options = {}) { + let wasProgrammaticallyResized = false; + let resize = null; + useEffect( + (el) => { + if (el) { + resize = (programmaticResize = false) => { + wasProgrammaticallyResized = programmaticResize; + if (el instanceof HTMLInputElement) { + resizeInput(el, options); + } else { + resizeTextArea(el, options); + } + if (options.onResize) { + options.onResize(el, options); + } + }; + el.addEventListener("input", () => resize(true)); + const resizeObserver = new ResizeObserver(() => { + // This ensures that the resize function is not called twice on input or page load + if (wasProgrammaticallyResized) { + wasProgrammaticallyResized = false; + return; + } + resize(); + }); + resizeObserver.observe(el); + return () => { + el.removeEventListener("input", resize); + resizeObserver.unobserve(el); + resizeObserver.disconnect(); + resize = null; + }; + } + }, + () => [ref.el] + ); + useEffect(() => { + if (resize) { + resize(true); + } + }); +} diff --git a/web_widget_product_label_section_and_note/static/src/core/utils/product_and_label_autoresize.esm.js b/web_widget_product_label_section_and_note/static/src/core/utils/product_and_label_autoresize.esm.js new file mode 100644 index 000000000..f874025ca --- /dev/null +++ b/web_widget_product_label_section_and_note/static/src/core/utils/product_and_label_autoresize.esm.js @@ -0,0 +1,41 @@ +/** @odoo-module **/ +/* Copyright Odoo S.A. + * Copyright 2024 Tecnativa - Carlos Lopez + * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */ + +import {useAutoresize} from "./autoresize.esm"; + +export function productAndLabelResizeTextArea(textarea, options = {}) { + const style = window.getComputedStyle(textarea); + if (options.targetParentName) { + let target = textarea.parentElement; + let shouldContinue = true; + while (target && shouldContinue) { + const totalParentHeight = Array.from(target.children).reduce( + (total, child) => { + const childHeight = child.style.height || style.lineHeight; + return total + parseFloat(childHeight); + }, + 0 + ); + target.style.height = `${totalParentHeight}px`; + if (target.getAttribute("name") === options.targetParentName) { + shouldContinue = false; + } + target = target.parentElement; + } + } +} + +/** + * This overriden version of the resizeTextArea method is specificly done for the product_label_section_and_note widget + * His necessity is found in the fact that the cell of said widget doesn't contain only the input or textarea to resize + * but also another node containing the name of the product if said data is available. This means that the autoresize + * method which sets the height of the parent cell should sometimes add an additional row to the parent cell so that + * no text overflows + * + * @param {Ref} ref + */ +export function useProductAndLabelAutoresize(ref, options = {}) { + useAutoresize(ref, {onResize: productAndLabelResizeTextArea, ...options}); +}