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}); +}