diff --git a/README.md b/README.md
index a407905f837..d432b47492d 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ addon | version | maintainers | summary
[web_company_context_highlight](web_company_context_highlight/) | 19.0.1.0.0 |
| Highlight the company switcher when multiple companies are selected
[web_dark_mode](web_dark_mode/) | 19.0.1.0.0 | | Enabled Dark Mode for the Odoo Backend
[web_date_format_numeric](web_date_format_numeric/) | 19.0.1.0.0 | | Display all date/datetime fields in numeric format
-[web_dialog_size](web_dialog_size/) | 19.0.1.0.0 | | A module that lets the user expand a dialog box to the full screen width.
+[web_dialog_size](web_dialog_size/) | 19.0.1.0.1 | | A module that lets the user expand a dialog box to the full screen width.
[web_editor_class_selector](web_editor_class_selector/) | 19.0.1.0.0 |
| Web editor class selector
[web_environment_ribbon](web_environment_ribbon/) | 19.0.1.0.0 | | Web Environment Ribbon
[web_favicon](web_favicon/) | 19.0.1.0.0 | | Allows to set a custom shortcut icon (aka favicon)
@@ -47,7 +47,8 @@ addon | version | maintainers | summary
[web_timeline](web_timeline/) | 19.0.1.0.0 | | Interactive visualization chart to show events in time
[web_tree_many2one_clickable](web_tree_many2one_clickable/) | 19.0.1.0.0 | | Open the linked resource when clicking on their name
[web_widget_bokeh_chart](web_widget_bokeh_chart/) | 19.0.1.0.1 |
| This widget allows to display charts using Bokeh library.
-[web_widget_one2many_tree_line_duplicate](web_widget_one2many_tree_line_duplicate/) | 19.0.1.0.0 | | Web Widget One2many Tree Line Duplicate
+[web_widget_numeric_step](web_widget_numeric_step/) | 19.0.1.0.0 |
| Web Widget Numeric Step
+[web_widget_one2many_tree_line_duplicate](web_widget_one2many_tree_line_duplicate/) | 19.0.1.0.1 | | Web Widget One2many Tree Line Duplicate
[web_widget_product_label_section_and_note_full_label](web_widget_product_label_section_and_note_full_label/) | 19.0.1.0.0 | | Display the full label in the product_label_section_and_note widget.
[web_widget_product_label_section_and_note_name_visibility](web_widget_product_label_section_and_note_name_visibility/) | 19.0.1.0.0 |
| Alternate the visibility of the product and description.
[web_widget_section_and_note_text_scrollable](web_widget_section_and_note_text_scrollable/) | 19.0.1.0.0 |
| Make the text field of Section and Note widget scrollable
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml
index 3500ebf845d..8d5b0dc06cc 100644
--- a/setup/_metapackage/pyproject.toml
+++ b/setup/_metapackage/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "odoo-addons-oca-web"
-version = "19.0.20260825.0"
+version = "19.0.20260828.0"
dependencies = [
"odoo-addon-web_calendar_slot_duration==19.0.*",
"odoo-addon-web_chatter_position==19.0.*",
@@ -28,6 +28,7 @@ dependencies = [
"odoo-addon-web_timeline==19.0.*",
"odoo-addon-web_tree_many2one_clickable==19.0.*",
"odoo-addon-web_widget_bokeh_chart==19.0.*",
+ "odoo-addon-web_widget_numeric_step==19.0.*",
"odoo-addon-web_widget_one2many_tree_line_duplicate==19.0.*",
"odoo-addon-web_widget_product_label_section_and_note_full_label==19.0.*",
"odoo-addon-web_widget_product_label_section_and_note_name_visibility==19.0.*",
diff --git a/web_dialog_size/README.rst b/web_dialog_size/README.rst
index d2a45b12b5c..124fef5b90a 100644
--- a/web_dialog_size/README.rst
+++ b/web_dialog_size/README.rst
@@ -11,7 +11,7 @@ Web Dialog Size
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:82dac89574909c0b51e36915e82361c0f70787d7b1b237ff449addfb0e5093da
+ !! source digest: sha256:c49a428f4ac5966464e95d7c3c89f8bdcc397ec405c21996eec3d20f7c87fbd7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/web_dialog_size/__manifest__.py b/web_dialog_size/__manifest__.py
index 19fb6f15ee9..c80a05404cc 100644
--- a/web_dialog_size/__manifest__.py
+++ b/web_dialog_size/__manifest__.py
@@ -15,7 +15,7 @@
"Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web",
"category": "web",
- "version": "19.0.1.0.0",
+ "version": "19.0.1.0.1",
"license": "AGPL-3",
"depends": ["web"],
"installable": True,
diff --git a/web_dialog_size/static/description/index.html b/web_dialog_size/static/description/index.html
index a56289ab9d3..802d13de630 100644
--- a/web_dialog_size/static/description/index.html
+++ b/web_dialog_size/static/description/index.html
@@ -372,7 +372,7 @@
Web Dialog Size
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:82dac89574909c0b51e36915e82361c0f70787d7b1b237ff449addfb0e5093da
+!! source digest: sha256:c49a428f4ac5966464e95d7c3c89f8bdcc397ec405c21996eec3d20f7c87fbd7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

A module that lets the user expand/restore the dialog box size through a
diff --git a/web_dialog_size/static/src/js/web_dialog_size.esm.js b/web_dialog_size/static/src/js/web_dialog_size.esm.js
index 07f4802392d..71cac2d5cab 100644
--- a/web_dialog_size/static/src/js/web_dialog_size.esm.js
+++ b/web_dialog_size/static/src/js/web_dialog_size.esm.js
@@ -6,6 +6,12 @@ import {patch} from "@web/core/utils/patch";
import {useService} from "@web/core/utils/hooks";
import {browser} from "@web/core/browser/browser";
+function triggerWindowResize() {
+ requestAnimationFrame(() => {
+ window.dispatchEvent(new Event("resize"));
+ });
+}
+
export class ExpandButton extends Component {
setup() {
this.original_size = this.props.getoriginalsize
@@ -18,13 +24,13 @@ export class ExpandButton extends Component {
dialog_button_extend() {
this.props.setsize("dialog_full_screen");
browser.localStorage.setItem("odoo.web_dialog_size.value", "true");
- this.render();
+ triggerWindowResize();
}
dialog_button_restore() {
this.props.setsize(this.original_size);
browser.localStorage.setItem("odoo.web_dialog_size.value", "false");
- this.render();
+ triggerWindowResize();
}
}
diff --git a/web_widget_numeric_step/README.rst b/web_widget_numeric_step/README.rst
new file mode 100644
index 00000000000..a4b871d0ba7
--- /dev/null
+++ b/web_widget_numeric_step/README.rst
@@ -0,0 +1,168 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+=======================
+Web Widget Numeric Step
+=======================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:9025ad79e52a5b2790d7a9406dc6aa4fd495d56b12bb3fea65d626b83ded7bb5
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
+ :target: https://github.com/OCA/web/tree/19.0/web_widget_numeric_step
+ :alt: OCA/web
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/web-19-0/web-19-0-web_widget_numeric_step
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=19.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This widget changes input number field and make it easier to increment
+the number thanks to 2 buttons (+ and -). Use JS native logic for input
+number, so you can use the options ``min``, ``max``, ``step``,
+``placeholder``.
+
+Demo available at Settings > Users & Companies > Users > \*Select One\*
+> See 'Credit Limit' field
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+In your xml view, add ``widget="numeric_step"`` This will add the 2
+buttons "+" and "-" just next to the input field in edit mode. Iteration
+step by default is 1.
+
+|image1|
+
+**Optional**
+
+Add an option to choose the step iteration and limits (min and max
+values).
+
+Example for an 0.25 step, min to -1 and max to 10 :
+
+.. code:: xml
+
+ days
+
+**Available Options**
+
+- step > Amount to increase/decrease (default: 1.0)
+- min > Min. value allowed (default: no limit)
+- max > Max. value allowed (default: no limit)
+- auto_select > Select the content when the element get focus (default:
+ False)
+- placeholder > Define the placeholder text (default: None)
+- class > Define additional classes for the input (default: None)
+
+**Examples**
+
+Iteration with 0.25 step, min to -1 and max to 10.
+
+Start to increment with button, continue incrementing with scrolling
+mouse.
+
+|image2|
+
+Iteration with 10 step, max limit 15, placeholder with onchange
+
+|image3|
+
+**Demo**
+
+You can test the feature by going to Settings > Technical > Scheduled
+Actions. The field "Execute Every" is configured to use the widget with
+2 step, min to 0 and max to 40.
+
+.. |image1| image:: https://raw.githubusercontent.com/OCA/web/19.0/web_widget_numeric_step/static/description/add_two_buttons.png
+.. |image2| image:: https://raw.githubusercontent.com/OCA/web/19.0/web_widget_numeric_step/static/description/step0,25andlimits.gif
+.. |image3| image:: https://raw.githubusercontent.com/OCA/web/19.0/web_widget_numeric_step/static/description/step10_limit15_placeholder117_with_onchange.gif
+
+Known issues / Roadmap
+======================
+
+When the value is entered using the keyboard, the limits set in the
+widget can be exceeded
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* GRAP
+* Tecnativa
+
+Contributors
+------------
+
+- `GRAP `__:
+
+ - Quentin DUPONT
+
+- `Tecnativa `__:
+
+ - Alexandre Díaz
+ - Carlos Roca
+
+- Helly kapatel
+- Thanakrit Pintana
+- Dhara Solanki
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px
+ :target: https://github.com/rafaelbn
+ :alt: rafaelbn
+.. |maintainer-yajo| image:: https://github.com/yajo.png?size=40px
+ :target: https://github.com/yajo
+ :alt: yajo
+
+Current `maintainers `__:
+
+|maintainer-rafaelbn| |maintainer-yajo|
+
+This module is part of the `OCA/web `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/web_widget_numeric_step/__init__.py b/web_widget_numeric_step/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/web_widget_numeric_step/__manifest__.py b/web_widget_numeric_step/__manifest__.py
new file mode 100644
index 00000000000..73cf7400e3e
--- /dev/null
+++ b/web_widget_numeric_step/__manifest__.py
@@ -0,0 +1,22 @@
+# Copyright 2019 GRAP - Quentin DUPONT
+# Copyright 2020 Tecnativa - Alexandre Díaz
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+
+{
+ "name": "Web Widget Numeric Step",
+ "category": "web",
+ "version": "19.0.1.0.0",
+ "author": "GRAP, Tecnativa, Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "website": "https://github.com/OCA/web",
+ "depends": ["web"],
+ "demo": ["demo/ir_cron.xml"],
+ "assets": {
+ "web.assets_backend": [
+ "web_widget_numeric_step/static/src/*",
+ ],
+ },
+ "maintainers": ["rafaelbn", "yajo"],
+ "auto_install": False,
+ "installable": True,
+}
diff --git a/web_widget_numeric_step/demo/ir_cron.xml b/web_widget_numeric_step/demo/ir_cron.xml
new file mode 100644
index 00000000000..1a4f7bdc47e
--- /dev/null
+++ b/web_widget_numeric_step/demo/ir_cron.xml
@@ -0,0 +1,13 @@
+
+
+
+ ir.cron
+
+
+
+ numeric_step
+ {'step': 2, 'min': 0, 'max': 40}
+
+
+
+
diff --git a/web_widget_numeric_step/i18n/de.po b/web_widget_numeric_step/i18n/de.po
new file mode 100644
index 00000000000..d16afa183f6
--- /dev/null
+++ b/web_widget_numeric_step/i18n/de.po
@@ -0,0 +1,39 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_numeric_step
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2021-10-13 20:46+0000\n"
+"Last-Translator: Corneliuus \n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.3.2\n"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Minus"
+msgstr "Minus"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.esm.js:0
+msgid "Numeric Step"
+msgstr ""
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Plus"
+msgstr "Plus"
+
+#, python-format
+#~ msgid "Value"
+#~ msgstr "Wert"
diff --git a/web_widget_numeric_step/i18n/es.po b/web_widget_numeric_step/i18n/es.po
new file mode 100644
index 00000000000..ea627175cfb
--- /dev/null
+++ b/web_widget_numeric_step/i18n/es.po
@@ -0,0 +1,39 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_numeric_step
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2023-10-15 20:36+0000\n"
+"Last-Translator: Ivorra78 \n"
+"Language-Team: none\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.17\n"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Minus"
+msgstr "Menos"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.esm.js:0
+msgid "Numeric Step"
+msgstr "Paso Numérico"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Plus"
+msgstr "Más"
+
+#, python-format
+#~ msgid "Value"
+#~ msgstr "Valor"
diff --git a/web_widget_numeric_step/i18n/fr.po b/web_widget_numeric_step/i18n/fr.po
new file mode 100644
index 00000000000..c47a151521a
--- /dev/null
+++ b/web_widget_numeric_step/i18n/fr.po
@@ -0,0 +1,35 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_number_ux_choice
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-08-30 12:07+0000\n"
+"PO-Revision-Date: 2019-08-30 12:07+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Minus"
+msgstr "Moins"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.esm.js:0
+msgid "Numeric Step"
+msgstr ""
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Plus"
+msgstr "Plus"
diff --git a/web_widget_numeric_step/i18n/it.po b/web_widget_numeric_step/i18n/it.po
new file mode 100644
index 00000000000..c56a4c0d27f
--- /dev/null
+++ b/web_widget_numeric_step/i18n/it.po
@@ -0,0 +1,39 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_numeric_step
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 13.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2023-11-27 11:33+0000\n"
+"Last-Translator: mymage \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.17\n"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Minus"
+msgstr "Meno"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.esm.js:0
+msgid "Numeric Step"
+msgstr "Passo numerico"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Plus"
+msgstr "Più"
+
+#, python-format
+#~ msgid "Value"
+#~ msgstr "Valore"
diff --git a/web_widget_numeric_step/i18n/web_widget_numeric_step.pot b/web_widget_numeric_step/i18n/web_widget_numeric_step.pot
new file mode 100644
index 00000000000..04ad3512428
--- /dev/null
+++ b/web_widget_numeric_step/i18n/web_widget_numeric_step.pot
@@ -0,0 +1,32 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_numeric_step
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 19.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Minus"
+msgstr ""
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.esm.js:0
+msgid "Numeric Step"
+msgstr ""
+
+#. module: web_widget_numeric_step
+#. odoo-javascript
+#: code:addons/web_widget_numeric_step/static/src/numeric_step.xml:0
+msgid "Plus"
+msgstr ""
diff --git a/web_widget_numeric_step/pyproject.toml b/web_widget_numeric_step/pyproject.toml
new file mode 100644
index 00000000000..4231d0cccb3
--- /dev/null
+++ b/web_widget_numeric_step/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/web_widget_numeric_step/readme/CONTRIBUTORS.md b/web_widget_numeric_step/readme/CONTRIBUTORS.md
new file mode 100644
index 00000000000..3ab6f274527
--- /dev/null
+++ b/web_widget_numeric_step/readme/CONTRIBUTORS.md
@@ -0,0 +1,8 @@
+- [GRAP](http://www.grap.coop):
+ - Quentin DUPONT \<\>
+- [Tecnativa](https://www.tecnativa.com/):
+ - Alexandre Díaz
+ - Carlos Roca
+- Helly kapatel \<\>
+- Thanakrit Pintana \<\>
+- Dhara Solanki \<\>
diff --git a/web_widget_numeric_step/readme/DESCRIPTION.md b/web_widget_numeric_step/readme/DESCRIPTION.md
new file mode 100644
index 00000000000..c679491a009
--- /dev/null
+++ b/web_widget_numeric_step/readme/DESCRIPTION.md
@@ -0,0 +1,6 @@
+This widget changes input number field and make it easier to increment
+the number thanks to 2 buttons (+ and -). Use JS native logic for input
+number, so you can use the options `min`, `max`, `step`, `placeholder`.
+
+Demo available at Settings \> Users & Companies \> Users \> \*Select
+One\* \> See 'Credit Limit' field
diff --git a/web_widget_numeric_step/readme/ROADMAP.md b/web_widget_numeric_step/readme/ROADMAP.md
new file mode 100644
index 00000000000..953fb14c997
--- /dev/null
+++ b/web_widget_numeric_step/readme/ROADMAP.md
@@ -0,0 +1 @@
+When the value is entered using the keyboard, the limits set in the widget can be exceeded
diff --git a/web_widget_numeric_step/readme/USAGE.md b/web_widget_numeric_step/readme/USAGE.md
new file mode 100644
index 00000000000..5d5e2d88b4a
--- /dev/null
+++ b/web_widget_numeric_step/readme/USAGE.md
@@ -0,0 +1,45 @@
+In your xml view, add `widget="numeric_step"` This will add the 2
+buttons "+" and "-" just next to the input field in edit mode. Iteration
+step by default is 1.
+
+
+
+**Optional**
+
+Add an option to choose the step iteration and limits (min and max
+values).
+
+Example for an 0.25 step, min to -1 and max to 10 :
+
+``` xml
+ days
+```
+
+**Available Options**
+
+- step \> Amount to increase/decrease (default: 1.0)
+- min \> Min. value allowed (default: no limit)
+- max \> Max. value allowed (default: no limit)
+- auto_select \> Select the content when the element get focus (default:
+ False)
+- placeholder \> Define the placeholder text (default: None)
+- class \> Define additional classes for the input (default: None)
+
+**Examples**
+
+Iteration with 0.25 step, min to -1 and max to 10.
+
+Start to increment with button, continue incrementing with scrolling
+mouse.
+
+
+
+Iteration with 10 step, max limit 15, placeholder with onchange
+
+
+
+**Demo**
+
+You can test the feature by going to Settings > Technical > Scheduled Actions. The
+field "Execute Every" is configured to use the widget with 2 step, min to 0 and max
+to 40.
\ No newline at end of file
diff --git a/web_widget_numeric_step/static/description/add_two_buttons.png b/web_widget_numeric_step/static/description/add_two_buttons.png
new file mode 100644
index 00000000000..7a60f2dea9b
Binary files /dev/null and b/web_widget_numeric_step/static/description/add_two_buttons.png differ
diff --git a/web_widget_numeric_step/static/description/icon.png b/web_widget_numeric_step/static/description/icon.png
new file mode 100644
index 00000000000..3a662b992dd
Binary files /dev/null and b/web_widget_numeric_step/static/description/icon.png differ
diff --git a/web_widget_numeric_step/static/description/index.html b/web_widget_numeric_step/static/description/index.html
new file mode 100644
index 00000000000..254fdf7cf8f
--- /dev/null
+++ b/web_widget_numeric_step/static/description/index.html
@@ -0,0 +1,490 @@
+
+
+
+
+
+README.rst
+
+
+
+
+
+
diff --git a/web_widget_numeric_step/static/description/step0,25andlimits.gif b/web_widget_numeric_step/static/description/step0,25andlimits.gif
new file mode 100644
index 00000000000..ce6780e1b75
Binary files /dev/null and b/web_widget_numeric_step/static/description/step0,25andlimits.gif differ
diff --git a/web_widget_numeric_step/static/description/step10_limit15_placeholder117_with_onchange.gif b/web_widget_numeric_step/static/description/step10_limit15_placeholder117_with_onchange.gif
new file mode 100644
index 00000000000..709498e3cf3
Binary files /dev/null and b/web_widget_numeric_step/static/description/step10_limit15_placeholder117_with_onchange.gif differ
diff --git a/web_widget_numeric_step/static/src/numeric_step.esm.js b/web_widget_numeric_step/static/src/numeric_step.esm.js
new file mode 100644
index 00000000000..002591b87f7
--- /dev/null
+++ b/web_widget_numeric_step/static/src/numeric_step.esm.js
@@ -0,0 +1,89 @@
+import {FloatField} from "@web/views/fields/float/float_field";
+import {_t} from "@web/core/l10n/translation";
+import {registry} from "@web/core/registry";
+import {standardFieldProps} from "@web/views/fields/standard_field_props";
+
+export class NumericStep extends FloatField {
+ setup() {
+ super.setup();
+ }
+ _onStepClick(ev) {
+ const mode = ev.target.dataset.mode;
+ this._doStep(mode);
+ }
+ _onKeyDown(ev) {
+ if (ev.keyCode === 38) {
+ this._doStep("plus");
+ } else if (ev.keyCode === 40) {
+ this._doStep("minus");
+ }
+ }
+ _onWheel(ev) {
+ ev.preventDefault();
+ if (!this._lastWheelTime) {
+ this._lastWheelTime = 0;
+ }
+ const now = Date.now();
+ const throttleLimit = 100;
+ if (now - this._lastWheelTime >= throttleLimit) {
+ this._lastWheelTime = now;
+
+ if (ev.deltaY > 0) {
+ this._doStep("minus");
+ } else {
+ this._doStep("plus");
+ }
+ }
+ }
+ updateField(val) {
+ return this.props.record.update({[this.props.name]: val});
+ }
+ _doStep(mode) {
+ let cval = this.props.record.data[this.props.name];
+ if (mode === "plus") {
+ cval += this.props.step;
+ } else if (mode === "minus") {
+ cval -= this.props.step;
+ }
+ if (cval < this.props.min) {
+ cval = this.props.min;
+ } else if (cval > this.props.max) {
+ cval = this.props.max;
+ }
+ this.updateField(cval);
+ }
+}
+
+NumericStep.template = "web_widget_numeric_step";
+NumericStep.props = {
+ ...standardFieldProps,
+ inputType: {type: String, optional: true},
+ step: {type: Number, optional: true},
+ min: {type: Number, optional: true},
+ max: {type: Number, optional: true},
+ placeholder: {type: String, optional: true},
+ additional_class: {type: String, optional: true},
+};
+NumericStep.defaultProps = {
+ ...FloatField.defaultProps,
+ inputType: "text",
+};
+
+export const numericStep = {
+ component: NumericStep,
+ supportedTypes: ["float"],
+ displayName: _t("Numeric Step"),
+ extractProps: ({attrs, options}) => {
+ return {
+ name: attrs.name,
+ inputType: attrs.type,
+ step: options.step || 1,
+ min: options.min,
+ max: options.max,
+ placeholder: attrs.placeholder,
+ additional_class: attrs.class,
+ };
+ },
+};
+
+registry.category("fields").add("numeric_step", numericStep);
diff --git a/web_widget_numeric_step/static/src/numeric_step.scss b/web_widget_numeric_step/static/src/numeric_step.scss
new file mode 100644
index 00000000000..04a55184f07
--- /dev/null
+++ b/web_widget_numeric_step/static/src/numeric_step.scss
@@ -0,0 +1,22 @@
+// Copyright 2023 Moduon Team S.L.
+// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
+
+.widget_numeric_step {
+ // Hide the buttons until the user hovers if possible
+ @media (hover: hover) {
+ .btn_numeric_step {
+ visibility: hidden;
+ }
+ }
+ &:hover .btn_numeric_step {
+ visibility: visible;
+ }
+}
+
+.o_numeric_step_cell {
+ // Default for old browsers
+ min-width: 15 * $btn-padding-x;
+ // Value hardcoded in `FIXED_FIELD_COLUMN_WIDTHS.float` + width of 2
+ // FontAwesome icons + 2 buttons * 2 horizontal paddings
+ min-width: calc(92px + 2ex + 4 * #{$btn-padding-x});
+}
diff --git a/web_widget_numeric_step/static/src/numeric_step.xml b/web_widget_numeric_step/static/src/numeric_step.xml
new file mode 100644
index 00000000000..f6aa28637b9
--- /dev/null
+++ b/web_widget_numeric_step/static/src/numeric_step.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
diff --git a/web_widget_one2many_tree_line_duplicate/README.rst b/web_widget_one2many_tree_line_duplicate/README.rst
index 1bb82515397..3b8f5387efc 100644
--- a/web_widget_one2many_tree_line_duplicate/README.rst
+++ b/web_widget_one2many_tree_line_duplicate/README.rst
@@ -11,7 +11,7 @@ Web Widget One2many Tree Line Duplicate
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:e5b95135142c797c5a7addebf73d134f9acbec555ff441d0c2baf718accd04f0
+ !! source digest: sha256:3b891d75bd44e3d0168c0baf1a0dc436298293a61dc316fe13c5f60afb0b3ff4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/web_widget_one2many_tree_line_duplicate/__manifest__.py b/web_widget_one2many_tree_line_duplicate/__manifest__.py
index 7389a638daf..0fc49b5824a 100644
--- a/web_widget_one2many_tree_line_duplicate/__manifest__.py
+++ b/web_widget_one2many_tree_line_duplicate/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "Web Widget One2many Tree Line Duplicate",
"category": "web",
- "version": "19.0.1.0.0",
+ "version": "19.0.1.0.1",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/web",
diff --git a/web_widget_one2many_tree_line_duplicate/static/description/index.html b/web_widget_one2many_tree_line_duplicate/static/description/index.html
index ab0938d05c1..22afc18a8f9 100644
--- a/web_widget_one2many_tree_line_duplicate/static/description/index.html
+++ b/web_widget_one2many_tree_line_duplicate/static/description/index.html
@@ -372,7 +372,7 @@ Web Widget One2many Tree Line Duplicate
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:e5b95135142c797c5a7addebf73d134f9acbec555ff441d0c2baf718accd04f0
+!! source digest: sha256:3b891d75bd44e3d0168c0baf1a0dc436298293a61dc316fe13c5f60afb0b3ff4
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow to add a icon to clone the line.
diff --git a/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.esm.js b/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.esm.js
index 5ffd8ba6355..333d3beaef0 100644
--- a/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.esm.js
+++ b/web_widget_one2many_tree_line_duplicate/static/src/list/list_renderer.esm.js
@@ -5,6 +5,20 @@ import {ListRenderer} from "@web/views/list/list_renderer";
import {patch} from "@web/core/utils/patch";
import {exprToBoolean} from "@web/core/utils/strings";
import {browser} from "@web/core/browser/browser";
+import {x2ManyCommands} from "@web/core/orm_service";
+
+const SCALAR_TYPES = [
+ "integer",
+ "float",
+ "monetary",
+ "char",
+ "text",
+ "boolean",
+ "selection",
+ "date",
+ "datetime",
+];
+const MANY2ONE_TYPES = ["many2one", "many2one_reference", "reference"];
patch(ListRenderer.prototype, {
setup() {
@@ -47,40 +61,146 @@ patch(ListRenderer.prototype, {
}
return nbCols;
},
- async onCloneIconClick(record) {
- const list = this.props.list;
- const left = await list.leaveEditMode();
- if (!left) {
- return;
- }
- // Snapshot of the source's scalar values, including the readonly computed
- // ones (subtotals...), so the copy is exact. duplicateRecords creates the
- // line running its onchanges, which recompute editable fields (e.g.
- // price_unit from the product) and discard the manual values. We re-apply
- // this snapshot with withoutOnchange so those values are kept.
- const scalarTypes = [
- "integer",
- "float",
- "monetary",
- "char",
- "text",
- "boolean",
- "selection",
- "date",
- "datetime",
- ];
+ /**
+ * Snapshot of the record's values, including the readonly computed ones
+ * (subtotals...), so the copy is exact. duplicateRecords creates the line
+ * running its onchanges, which recompute editable fields (e.g. price_unit
+ * from the product) and discard the manual values. We re-apply this
+ * snapshot with withoutOnchange so those values are kept.
+ *
+ * @param {Record} record record to copy the values from
+ * @param {StaticList} list list the record belongs to
+ * @returns {Object} changes to apply on the copy
+ */
+ getDuplicateSnapshot(record, list) {
const snapshot = {};
for (const [name, value] of Object.entries(record.data)) {
const field = record.fields[name];
if (
- field &&
- name !== "display_name" &&
- name !== list.handleField &&
- scalarTypes.includes(field.type)
+ !field ||
+ name === "display_name" ||
+ name === list.handleField ||
+ name === list.config.relationField
) {
+ continue;
+ }
+ if (SCALAR_TYPES.includes(field.type)) {
snapshot[name] = value;
+ } else if (MANY2ONE_TYPES.includes(field.type)) {
+ // Always invisible many2ones are read as a plain id (see
+ // getFieldsSpec), so we wrap it and let the model complete the
+ // value (display_name...) when applying the change. The rest are
+ // copied as a new object, so both records don't share it.
+ snapshot[name] =
+ typeof value === "number"
+ ? {id: value}
+ : value && Object.assign({}, value);
+ } else if (field.type === "many2many") {
+ // SET (and not LINK) so the copy ends up with exactly the same
+ // records as the source: this way we also drop the ones the
+ // onchange added on its own.
+ snapshot[name] = [x2ManyCommands.set([...value.currentIds])];
+ }
+ // One2many fields can't be set through a snapshot: they are copied
+ // sub-record by sub-record in duplicateOne2manyValues().
+ }
+ return snapshot;
+ },
+ /**
+ * Fetches the values of the sub-records of a one2many that is always
+ * invisible in the list: those are read with their ids only (see
+ * getFieldsSpec), so their datapoints are empty and copying them as they are
+ * would end up creating empty (and usually invalid) records.
+ *
+ * @param {Record} record source record
+ * @param {String} fieldName name of the one2many field
+ */
+ async loadOne2manyValues(record, fieldName) {
+ const invisible = record.activeFields[fieldName].invisible;
+ if (invisible !== "True" && invisible !== "1") {
+ return;
+ }
+ const list = record.data[fieldName];
+ const lines = list.records.filter((line) => line.resId);
+ if (!lines.length) {
+ return;
+ }
+ const values = await list.model._loadRecords(
+ {...list.config, resIds: lines.map((line) => line.resId)},
+ list.evalContext
+ );
+ for (const line of lines) {
+ const lineValues = values.find((vals) => vals.id === line.resId);
+ if (lineValues) {
+ line._applyValues(lineValues);
+ }
+ }
+ },
+ /**
+ * Recreates on the copy the sub-records of every one2many field of the
+ * source. They are the only values the snapshot can't carry, as each
+ * sub-record has to be created (and filled) on its own. The lines the
+ * onchanges may have generated on the copy are dropped first, so both
+ * records end up with exactly the same content.
+ *
+ * @param {Record} record source record
+ * @param {Record} newRecord copy of the source record
+ */
+ async duplicateOne2manyValues(record, newRecord) {
+ for (const [name, sourceList] of Object.entries(record.data)) {
+ const field = record.fields[name];
+ if (!field || field.type !== "one2many" || !(name in newRecord.data)) {
+ continue;
+ }
+ const targetList = newRecord.data[name];
+ if (
+ !Object.keys(sourceList.activeFields).length ||
+ !Object.keys(targetList.activeFields).length
+ ) {
+ // The sub-records have no fields to copy (the field has no
+ // sub-view here).
+ continue;
}
+ await this.loadOne2manyValues(record, name);
+ if (targetList.records.length) {
+ // DELETE for the lines the onchange created on the fly, UNLINK
+ // for the existing ones it linked: this way we only discard the
+ // pending commands, without touching any stored record.
+ await targetList._applyCommands(
+ targetList.records.map((line) =>
+ line.resId
+ ? [x2ManyCommands.UNLINK, line.resId]
+ : [x2ManyCommands.DELETE, line._virtualId]
+ )
+ );
+ }
+ for (const line of sourceList.records) {
+ const newLine = await targetList._createNewRecordDatapoint({
+ mode: "readonly",
+ });
+ const snapshot = this.getDuplicateSnapshot(line, targetList);
+ if (Object.keys(snapshot).length) {
+ await newLine._update(snapshot, {
+ withoutOnchange: true,
+ withoutParentUpdate: true,
+ });
+ }
+ // Nested one2manys are copied the same way, recursively.
+ await this.duplicateOne2manyValues(line, newLine);
+ // Added at the end, like the core does when duplicating: the
+ // record is filled before being part of the list, so the
+ // intermediate values are never rendered.
+ await targetList._addRecord(newLine, {position: "bottom"});
+ }
+ }
+ },
+ async onCloneIconClick(record) {
+ const list = this.props.list;
+ const left = await list.leaveEditMode();
+ if (!left) {
+ return;
}
+ const snapshot = this.getDuplicateSnapshot(record, list);
// Run everything in a single model transaction and notify only once, at
// the end, so the intermediate (onchange-recomputed) values are never
// rendered: the line appears directly with the original values, without
@@ -91,15 +211,14 @@ patch(ListRenderer.prototype, {
// The duplicate is inserted right after the source line; identify it
// by position (references aren't stable across the re-wrapping).
const newRecord = list.records[sourceIndex + 1];
- if (
- newRecord &&
- newRecord.id !== record.id &&
- Object.keys(snapshot).length
- ) {
- await newRecord._update(snapshot, {
- withoutOnchange: true,
- withoutParentUpdate: true,
- });
+ if (newRecord && newRecord.id !== record.id) {
+ if (Object.keys(snapshot).length) {
+ await newRecord._update(snapshot, {
+ withoutOnchange: true,
+ withoutParentUpdate: true,
+ });
+ }
+ await this.duplicateOne2manyValues(record, newRecord);
}
await list._onUpdate();
});