From de0d409efcc206cd408b784bf4355ef5b475b7b5 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Wed, 3 Aug 2022 13:46:11 +0000 Subject: [PATCH] [IMP] web: Add params to useAutofocus This commit introduces an object parameter to the "useAutofocus" hook, that has 2 new optional keys: - "refName": to override the ref "autofocus" (effectively allows to run the hook multiple times) - "selectAll": selects the whole value of the input/textarea (instead of putting the cursor at the end). Part-of: odoo/odoo#97411 --- addons/web/static/src/core/utils/hooks.js | 12 ++- .../static/tests/core/utils/hooks_tests.js | 95 +++++++++++++++---- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/addons/web/static/src/core/utils/hooks.js b/addons/web/static/src/core/utils/hooks.js index 10c339bdf3f..2fe6743a5c0 100644 --- a/addons/web/static/src/core/utils/hooks.js +++ b/addons/web/static/src/core/utils/hooks.js @@ -27,11 +27,14 @@ const { status, useComponent, useEffect, useRef, onWillUnmount } = owl; * Focus an element referenced by a t-ref="autofocus" in the current component * as soon as it appears in the DOM and if it was not displayed before. * If it is an input/textarea, set the selection at the end. + * @param {Object} [params] + * @param {string} [params.refName] override the ref name "autofocus" + * @param {boolean} [params.selectAll] if true, will select the entire text value. * @returns {Object} the element reference */ -export function useAutofocus() { +export function useAutofocus({ refName, selectAll } = {}) { const comp = useComponent(); - const ref = useRef("autofocus"); + const ref = useRef(refName || "autofocus"); // Prevent autofocus in mobile if (comp.env.isSmall) { return ref; @@ -45,8 +48,9 @@ export function useAutofocus() { (el) => { if (el) { el.focus(); - if (["INPUT", "TEXTAREA"].includes(el.tagName) && el.type !== 'number') { - el.selectionStart = el.selectionEnd = el.value.length; + if (["INPUT", "TEXTAREA"].includes(el.tagName) && el.type !== "number") { + el.selectionEnd = el.value.length; + el.selectionStart = selectAll ? 0 : el.value.length; } } }, diff --git a/addons/web/static/tests/core/utils/hooks_tests.js b/addons/web/static/tests/core/utils/hooks_tests.js index 7790f39d37e..4246df709f4 100644 --- a/addons/web/static/tests/core/utils/hooks_tests.js +++ b/addons/web/static/tests/core/utils/hooks_tests.js @@ -14,7 +14,7 @@ import { } from "@web/../tests/helpers/utils"; import { LegacyComponent } from "@web/legacy/legacy_component"; -const { Component, onMounted, xml } = owl; +const { Component, onMounted, useState, xml } = owl; const serviceRegistry = registry.category("services"); QUnit.module("utils", () => { @@ -47,30 +47,33 @@ QUnit.module("utils", () => { assert.strictEqual(document.activeElement, comp.inputRef.el); }); - QUnit.test("useAutofocus: simple usecase when input type is number", async function (assert) { - class MyComponent extends Component { - setup() { - this.inputRef = useAutofocus(); + QUnit.test( + "useAutofocus: simple usecase when input type is number", + async function (assert) { + class MyComponent extends Component { + setup() { + this.inputRef = useAutofocus(); + } } - } - MyComponent.template = xml` + MyComponent.template = xml` `; - registry.category("services").add("ui", uiService); + registry.category("services").add("ui", uiService); - const env = await makeTestEnv(); - const target = getFixture(); - const comp = await mount(MyComponent, target, { env }); + const env = await makeTestEnv(); + const target = getFixture(); + const comp = await mount(MyComponent, target, { env }); - assert.strictEqual(document.activeElement, comp.inputRef.el); + assert.strictEqual(document.activeElement, comp.inputRef.el); - comp.render(); - await nextTick(); - assert.strictEqual(document.activeElement, comp.inputRef.el); - }); + comp.render(); + await nextTick(); + assert.strictEqual(document.activeElement, comp.inputRef.el); + } + ); QUnit.test("useAutofocus: conditional autofocus", async function (assert) { class MyComponent extends Component { @@ -124,7 +127,7 @@ QUnit.module("utils", () => { const fakeUIService = { start(env) { - let ui = {}; + const ui = {}; Object.defineProperty(env, "isSmall", { get() { return true; @@ -142,6 +145,64 @@ QUnit.module("utils", () => { await mount(MyComponent, target, { env }); }); + QUnit.test("supports different ref names", async (assert) => { + class MyComponent extends Component { + setup() { + this.secondRef = useAutofocus({ refName: "second" }); + this.firstRef = useAutofocus({ refName: "first" }); + + this.state = useState({ showSecond: true }); + } + } + MyComponent.template = xml` + + + + + `; + + registry.category("services").add("ui", uiService); + + const env = await makeTestEnv(); + const target = getFixture(); + const comp = await mount(MyComponent, target, { env }); + await nextTick(); + + // "first" is focused first since it has the last call to "useAutofocus" + assert.strictEqual(document.activeElement, comp.firstRef.el); + + comp.state.showSecond = false; + await nextTick(); + comp.state.showSecond = true; + await nextTick(); + + assert.strictEqual(document.activeElement, comp.secondRef.el); + }); + + QUnit.test("can select an entire text", async (assert) => { + class MyComponent extends Component { + setup() { + this.inputRef = useAutofocus({ selectAll: true }); + } + } + MyComponent.template = xml` + + + + `; + + registry.category("services").add("ui", uiService); + + const env = await makeTestEnv(); + const target = getFixture(); + const comp = await mount(MyComponent, target, { env }); + await nextTick(); + + assert.strictEqual(document.activeElement, comp.inputRef.el); + assert.strictEqual(comp.inputRef.el.selectionStart, 0); + assert.strictEqual(comp.inputRef.el.selectionEnd, 10); + }); + QUnit.module("useBus"); QUnit.test("useBus hook: simple usecase", async function (assert) {