[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
This commit is contained in:
Julien Mougenot
2022-08-25 12:05:02 +02:00
parent 338a974a2f
commit de0d409efc
2 changed files with 86 additions and 21 deletions
+8 -4
View File
@@ -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;
}
}
},
@@ -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`
<span>
<input type="number" t-ref="autofocus" />
</span>
`;
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`
<span>
<input type="text" t-ref="first" />
<input t-if="state.showSecond" type="text" t-ref="second" />
</span>
`;
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`
<span>
<input type="text" value="abcdefghij" t-ref="autofocus" />
</span>
`;
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) {