[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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user