From 585abd4847f3f712f849fd9ac9046a2a93282a7d Mon Sep 17 00:00:00 2001 From: luvi Date: Thu, 6 Apr 2023 08:55:15 +0000 Subject: [PATCH] [IMP] web: add bottomArea slot for SelectMenu This commit allows the customization of the bottom area of the SelectMenu component. The slot can show additional information, of handle any feature related to the state of the component, such as adding elements, etc. It can support any kind of content, so it could be used to display all kind of useful content. Two test have been added using this custom slot, asserting its presence and testing an interaction with its content. This changes is required to use the SelectMenu component in Knowledge, in task-3134491 closes odoo/odoo#117890 Signed-off-by: Aaron Bohy (aab) --- .../src/core/select_menu/select_menu.js | 4 + .../src/core/select_menu/select_menu.scss | 2 +- .../src/core/select_menu/select_menu.xml | 5 +- .../static/tests/core/select_menu_tests.js | 150 +++++++++++++++--- 4 files changed, 133 insertions(+), 28 deletions(-) diff --git a/addons/web/static/src/core/select_menu/select_menu.js b/addons/web/static/src/core/select_menu/select_menu.js index 6fe099b6a40..6c2b4edc987 100644 --- a/addons/web/static/src/core/select_menu/select_menu.js +++ b/addons/web/static/src/core/select_menu/select_menu.js @@ -81,6 +81,7 @@ export class SelectMenu extends Component { this.state = useState({ choices: [], displayedOptions: [], + searchValue: "", }); this.inputRef = useRef("inputRef"); this.inputContainerRef = useRef("inputContainerRef"); @@ -128,6 +129,8 @@ export class SelectMenu extends Component { } onOpened() { + this.state.searchValue = ""; + // Using useAutofocus inside the dropdown does not // work properly so we set the focus manually. if (this.inputRef.el) { @@ -157,6 +160,7 @@ export class SelectMenu extends Component { onInput(searchString) { this.filterOptions(searchString); + this.state.searchValue = searchString; // Get reference to dropdown container and scroll to the top. const inputContainer = this.inputContainerRef.el; diff --git a/addons/web/static/src/core/select_menu/select_menu.scss b/addons/web/static/src/core/select_menu/select_menu.scss index 5552f714f9a..0e54926f652 100644 --- a/addons/web/static/src/core/select_menu/select_menu.scss +++ b/addons/web/static/src/core/select_menu/select_menu.scss @@ -31,7 +31,7 @@ max-height: 350px; border: 1px solid $o-brand-primary; - .o_select_menu_input { + .o_select_menu_input, .o_select_menu_bottom_area { background-color: $dropdown-bg; } .o_select_menu_group { diff --git a/addons/web/static/src/core/select_menu/select_menu.xml b/addons/web/static/src/core/select_menu/select_menu.xml index 075685cafc8..b0d949e80c4 100644 --- a/addons/web/static/src/core/select_menu/select_menu.xml +++ b/addons/web/static/src/core/select_menu/select_menu.xml @@ -5,7 +5,7 @@ +
+ +
diff --git a/addons/web/static/tests/core/select_menu_tests.js b/addons/web/static/tests/core/select_menu_tests.js index d3fc357c614..07383ef4348 100644 --- a/addons/web/static/tests/core/select_menu_tests.js +++ b/addons/web/static/tests/core/select_menu_tests.js @@ -12,6 +12,7 @@ import { click, triggerEvent, nextTick, + editInput, } from "../helpers/utils"; import { Component, useState, xml } from "@odoo/owl"; @@ -226,30 +227,33 @@ QUnit.module("Web Components", (hooks) => { ); }); - QUnit.test("Value with no corresponding choices displays as if no choice was selected", async (assert) => { - class Parent extends Component { - static components = { SelectMenu }; - static template = xml` + QUnit.test( + "Value with no corresponding choices displays as if no choice was selected", + async (assert) => { + class Parent extends Component { + static components = { SelectMenu }; + static template = xml` `; - setup() { - this.choices = [ - { label: "World", value: "world" }, - { label: "Hello", value: "hello" }, - ]; - this.state = useState({ value: "coucou"}); + setup() { + this.choices = [ + { label: "World", value: "world" }, + { label: "Hello", value: "hello" }, + ]; + this.state = useState({ value: "coucou" }); + } + setValue(newValue) { + this.state.value = newValue; + } } - setValue(newValue) { - this.state.value = newValue; - } - } - await mount(Parent, target, { env }); - assert.equal(getValue(), "", `The toggler should be empty`); - }); + await mount(Parent, target, { env }); + assert.equal(getValue(), "", `The toggler should be empty`); + } + ); QUnit.test("Changing value props properly updates the selected choice", async (assert) => { class Parent extends Component { @@ -265,7 +269,7 @@ QUnit.module("Web Components", (hooks) => { { label: "Z", value: { hello: "world" } }, { label: "A", value: { paper: "company" } }, ]; - this.state = useState({ value: { paper: "company" }}); + this.state = useState({ value: { paper: "company" } }); } setValue(newValue) { this.state.value = newValue; @@ -273,11 +277,7 @@ QUnit.module("Web Components", (hooks) => { } const comp = await mount(Parent, target, { env }); - assert.equal( - getValue(), - "A", - `The select value shoud be "A"` - ); + assert.equal(getValue(), "A", `The select value shoud be "A"`); comp.setValue({ hello: "world" }); await nextTick(); @@ -325,7 +325,7 @@ QUnit.module("Web Components", (hooks) => { ); QUnit.test( - "When the \"required\" props is set to true, the clear button is not shown", + 'When the "required" props is set to true, the clear button is not shown', async (assert) => { class Parent extends Component { setup() { @@ -349,12 +349,20 @@ QUnit.module("Web Components", (hooks) => { `; const parent = await mount(Parent, target, { env }); - assert.containsNone(target, ".o_select_menu_toggler_clear", 'When the value is not set, there is no "clear" button'); + assert.containsNone( + target, + ".o_select_menu_toggler_clear", + 'When the value is not set, there is no "clear" button' + ); parent.setValue("hello"); await nextTick(); assert.strictEqual(getValue(), "Hello"); - assert.containsNone(target, ".o_select_menu_toggler_clear", 'When the value is set, there is no "clear" button'); + assert.containsNone( + target, + ".o_select_menu_toggler_clear", + 'When the value is set, there is no "clear" button' + ); } ); @@ -442,6 +450,96 @@ QUnit.module("Web Components", (hooks) => { assert.strictEqual(target.querySelector(".coolClass").textContent, "Hello"); }); + QUnit.test( + "Custom template for the bottom area of the dropdown using a slot", + async (assert) => { + class Parent extends Component { + setup() { + this.choices = [ + { label: "Hello", value: "hello" }, + { label: "World", value: "world" }, + { label: "How", value: "how" }, + { label: "Are", value: "are" }, + { label: "You", value: "you" }, + ]; + this.state = useState({ value: ["world", "you"] }); + } + onSelect(newValue) { + this.state.value = newValue; + } + } + Parent.components = { SelectMenu }; + Parent.template = xml` + + Select something + + + items selected + + + + `; + + await mount(Parent, target, { env }); + await open(); + assert.strictEqual( + target.querySelector(".o_select_menu_bottom_area").textContent.trim(), + "2 items selected" + ); + + await click(target, ".o_select_menu_item:nth-child(2)"); + await open(); + assert.strictEqual( + target.querySelector(".o_select_menu_bottom_area").textContent.trim(), + "3 items selected" + ); + } + ); + + QUnit.test("Custom slot for the bottom area sends the current search value", async (assert) => { + class Parent extends Component { + setup() { + this.choices = [ + { label: "Hello", value: "hello" }, + { label: "World", value: "world" }, + ]; + } + onClick(value) { + assert.step(value + " clicked"); + } + } + Parent.components = { SelectMenu }; + Parent.template = xml` + + Select something + +
+ +
+
+
+ `; + + await mount(Parent, target, { env }); + await open(); + assert.containsNone(target, ".coolClass"); + + await editInput(target, ".o_select_menu_input input", "coucou"); + assert.containsOnce(target, ".coolClass"); + + await click(target.querySelector(".coolClass")); + assert.verifySteps(["coucou clicked"]); + }); + QUnit.test("Groups properly added in the select", async (assert) => { class Parent extends Component { setup() {