[IMP] web: focus of a X2many

When rewriting the Form view, we wanted the keyboard navigation using
"tab" and "shift+tab" to use the native browser behaviour. The purpose
of this commit is to remove the custom behaviour of X2many in a
form view when a "tab" or "shift-tab" make it be focused.

Before this commit, a new empty line would be created automatically.

After this commit, the button "Add a line" takes focus but no new line
is created.
If one wants to add one, that can be done by pressing "Enter".

Part-of: odoo/odoo#96101
This commit is contained in:
FrancoisGe
2022-07-28 01:29:58 +02:00
parent 05c66463bd
commit efad2216b3
3 changed files with 82 additions and 63 deletions
@@ -1377,44 +1377,6 @@ export class ListRenderer extends Component {
return false;
}
async onCreateAction(context) {
// TO DISCUSS: is it a use case for owl `batched()` ?
if (this.createProm) {
return;
}
this.props.onAdd({ context });
this.createProm = Promise.resolve();
this.createProm.then(() => {
this.lastCreatingAction = true;
});
await this.createProm;
this.createProm = null;
}
/**
* @param {FocusEvent & {
* target: HTMLElement,
* relatedTarget: HTMLElement | null
* }} ev
*/
onFocusIn(ev) {
const { relatedTarget, target } = ev;
const fromOutside = !this.rootRef.el.contains(relatedTarget);
if (!fromOutside) {
return;
}
const isX2MRowAdder =
target.tagName === "A" &&
target.parentElement.classList.contains("o_field_x2many_list_row_add");
const withinSameUIActiveElement =
this.uiService.getActiveElementOf(relatedTarget) === this.activeElement;
if (withinSameUIActiveElement && isX2MRowAdder) {
const { context } = this.creates[0];
this.onCreateAction(context);
}
}
setDirty(isDirty) {
this.lastIsDirty = isDirty;
}
@@ -6,7 +6,6 @@
class="o_list_renderer"
t-att-class="{'table-responsive': showTable}"
t-attf-class="{{ props.archInfo.className or '' }}"
t-on-focusin="onFocusIn"
tabindex="-1"
t-ref="root"
>
@@ -48,7 +47,7 @@
role="button"
t-att-class="create_index !== 0 ? 'ml16' : ''"
t-att-tabindex="props.list.editedRecord ? '-1' : '0'"
t-on-click.stop.prevent="() => this.onCreateAction(create.context)"
t-on-click.stop.prevent="() => this.props.onAdd({context: create.context})"
>
<t t-esc="create.description"/>
</a>
@@ -11671,7 +11671,7 @@ QUnit.module("Fields", (hooks) => {
);
QUnit.test(
"when Navigating to a one2many with tabs, it receives the focus and adds a new line",
"when Navigating to a one2many with tabs, the button add a line receives the focus",
async function (assert) {
await makeView({
type: "form",
@@ -11698,21 +11698,19 @@ QUnit.module("Fields", (hooks) => {
});
await clickEdit(target);
assert.strictEqual(target.querySelector("[name=qux] input"), document.activeElement);
getNextTabableElement(target).focus(); // go inside one2many
await nextTick();
assert.strictEqual(
target.querySelector("[name=turtle_foo] input"),
target.querySelector(".o_field_x2many_list_row_add a"),
document.activeElement
);
}
);
QUnit.test(
"when Navigating to a one2many with tabs, not filling any field and hitting tab, we should not add a first line but navigate to the next control",
"Navigate to a one2many with tab then tab again focus the next field",
async function (assert) {
serverData.models.partner.records[0].turtles = [];
@@ -11752,16 +11750,85 @@ QUnit.module("Fields", (hooks) => {
await nextTick();
assert.strictEqual(
target.querySelector(".o_field_x2many_list_row_add a"),
document.activeElement
);
assert.containsNone(target, "[name=turtles] .o_selected_row");
const nextInput = target.querySelector("[name=foo] input");
// trigger Tab event and check that the default behavior can happen.
const event = triggerEvent(
document.activeElement,
target.querySelector("[name=turtle_foo] input")
null,
"keydown",
{ key: "Tab" },
{ fast: true }
);
assert.strictEqual(getNextTabableElement(target), nextInput);
assert.ok(!event.defaultPrevented);
nextInput.focus();
await nextTick();
assert.strictEqual(document.activeElement, nextInput);
}
);
QUnit.test(
"when Navigating to a one2many with tabs, not filling any field and hitting tab, no line is added and the next field is focused",
async function (assert) {
serverData.models.partner.records[0].turtles = [];
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<sheet>
<group>
<field name="qux"/>
</group>
<notebook>
<page string="Partner page">
<field name="turtles">
<tree editable="bottom">
<field name="turtle_foo"/>
<field name="turtle_description"/>
</tree>
</field>
</page>
</notebook>
<group>
<field name="foo"/>
</group>
</sheet>
</form>`,
resId: 1,
});
await clickEdit(target);
assert.strictEqual(document.activeElement, target.querySelector("[name=qux] input"));
getNextTabableElement(target).focus(); // go inside one2many
await nextTick();
assert.strictEqual(
target.querySelector(".o_field_x2many_list_row_add a"),
document.activeElement
);
assert.containsNone(target, "[name=turtles] .o_selected_row");
await addRow(target);
assert.strictEqual(
target.querySelector("[name=turtle_foo] input"),
document.activeElement
);
triggerHotkey("Tab"); // go to turtle_description field
await nextTick();
assert.strictEqual(
document.activeElement,
target.querySelector("[name=turtle_description] textarea")
target.querySelector("[name=turtle_description] textarea"),
document.activeElement
);
const nextInput = target.querySelector("[name=foo] input");
@@ -11778,9 +11845,6 @@ QUnit.module("Fields", (hooks) => {
nextInput.focus();
await nextTick();
assert.strictEqual(document.activeElement, nextInput);
// the default behavior happens but the list renderer makes the edited record
// pass in readonly mode.
assert.containsNone(target, "[name=turtles] .o_selected_row");
}
);
@@ -11828,7 +11892,12 @@ QUnit.module("Fields", (hooks) => {
getNextTabableElement(target).focus(); // go inside one2many
await nextTick();
assert.strictEqual(
target.querySelector(".o_field_x2many_list_row_add a"),
document.activeElement
);
await addRow(target);
assert.strictEqual(
target.querySelector(".modal [name=turtle_foo] input"),
document.activeElement
@@ -11937,17 +12006,6 @@ QUnit.module("Fields", (hooks) => {
firstCreateActionLink.focus(); // goes inside one2many
await nextTick();
assert.containsOnce(target, "[name=p] .o_selected_row");
assert.strictEqual(
document.activeElement,
target.querySelector("[name=p] .o_selected_row input")
);
// press ESC to cancel 1st control click (create)
triggerHotkey("Escape");
await nextTick();
assert.containsNone(target, "[name=p] .o_selected_row");
assert.strictEqual(
document.activeElement,
target.querySelector(".o_field_x2many_list_row_add a")