diff --git a/addons/web/static/src/core/dropdown/dropdown.js b/addons/web/static/src/core/dropdown/dropdown.js
index ee6cbce04b5..2aec43dae01 100644
--- a/addons/web/static/src/core/dropdown/dropdown.js
+++ b/addons/web/static/src/core/dropdown/dropdown.js
@@ -305,6 +305,10 @@ Dropdown.props = {
optional: true,
validate: (prop) => ["parent"].includes(prop),
},
+ skipTogglerTabbing: {
+ type: Boolean,
+ optional: true,
+ },
startOpen: {
type: Boolean,
optional: true,
diff --git a/addons/web/static/src/core/dropdown/dropdown.xml b/addons/web/static/src/core/dropdown/dropdown.xml
index ba63d59e324..a015ca6a2ef 100644
--- a/addons/web/static/src/core/dropdown/dropdown.xml
+++ b/addons/web/static/src/core/dropdown/dropdown.xml
@@ -24,6 +24,7 @@
t-att-title="props.title"
t-att-data-hotkey="props.hotkey"
t-att-data-tooltip="props.tooltip"
+ t-att-tabindex="props.skipTogglerTabbing ? -1 : 0"
t-ref="togglerRef"
>
diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js
index d715063fc1b..b59dc39818e 100644
--- a/addons/web/static/src/views/list/list_renderer.js
+++ b/addons/web/static/src/views/list/list_renderer.js
@@ -1256,6 +1256,7 @@ export class ListRenderer extends Component {
futureRecord.switchMode("edit");
}
} else {
+ list.unselectRecord(true);
return false;
}
} else {
diff --git a/addons/web/static/src/views/list/list_renderer.xml b/addons/web/static/src/views/list/list_renderer.xml
index 7cae6da0396..a3f363587a0 100644
--- a/addons/web/static/src/views/list/list_renderer.xml
+++ b/addons/web/static/src/views/list/list_renderer.xml
@@ -47,6 +47,7 @@
diff --git a/addons/web/static/tests/core/dropdown_tests.js b/addons/web/static/tests/core/dropdown_tests.js
index 983204db796..93600e4b4c6 100644
--- a/addons/web/static/tests/core/dropdown_tests.js
+++ b/addons/web/static/tests/core/dropdown_tests.js
@@ -50,7 +50,7 @@ QUnit.module("Components", ({ beforeEach }) => {
await mount(Parent, target, { env });
assert.strictEqual(
target.querySelector(".dropdown").outerHTML,
- ''
+ ''
);
assert.containsOnce(target, "button.dropdown-toggle");
assert.containsNone(target, ".dropdown-menu");
diff --git a/addons/web/static/tests/helpers/utils.js b/addons/web/static/tests/helpers/utils.js
index e48dc2f256e..a450a88cfdb 100644
--- a/addons/web/static/tests/helpers/utils.js
+++ b/addons/web/static/tests/helpers/utils.js
@@ -489,6 +489,7 @@ export function editSelect(el, selector, value) {
* @param {string} hotkey
* @param {boolean} addOverlayModParts
* @param {KeyboardEventInit} eventAttrs
+ * @returns {{ keydownEvent: KeyboardEvent, keyupEvent: KeyboardEvent }}
*/
export function triggerHotkey(hotkey, addOverlayModParts = false, eventAttrs = {}) {
eventAttrs.key = hotkey.split("+").pop();
@@ -517,8 +518,11 @@ export function triggerHotkey(hotkey, addOverlayModParts = false, eventAttrs = {
eventAttrs.bubbles = true;
}
- document.activeElement.dispatchEvent(new KeyboardEvent("keydown", eventAttrs));
- document.activeElement.dispatchEvent(new KeyboardEvent("keyup", eventAttrs));
+ const keydownEvent = new KeyboardEvent("keydown", eventAttrs);
+ const keyupEvent = new KeyboardEvent("keyup", eventAttrs);
+ document.activeElement.dispatchEvent(keydownEvent);
+ document.activeElement.dispatchEvent(keyupEvent);
+ return { keydownEvent, keyupEvent };
}
export async function legacyExtraNextTick() {
diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js
index 4666df9b80a..97f13db74dd 100644
--- a/addons/web/static/tests/views/fields/one2many_field_tests.js
+++ b/addons/web/static/tests/views/fields/one2many_field_tests.js
@@ -6730,6 +6730,68 @@ QUnit.module("Fields", (hooks) => {
await clickSave(target);
});
+ QUnit.test(
+ "one2many list editable - should properly unselect the list field after shift+tab",
+ async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: /* xml */ `
+ `,
+ resId: 1,
+ });
+
+ await click(target, ".o_data_row td:first-child");
+ assert.containsOnce(target, ".o_selected_row", "should have selected row");
+ const { keydownEvent } = triggerHotkey("Shift+Tab");
+ await nextTick();
+ assert.containsNone(target, ".o_selected_row", "list should not be in edition");
+ // We also check the event is not default prevented, to make sure that the
+ // event flows and selection goes to the previous field.
+ assert.ok(!keydownEvent.defaultPrevented);
+ }
+ );
+
+ QUnit.test(
+ "one2many list editable - should not allow tab navigation focus on the optional field toggler",
+ async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: /* xml */ `
+ `,
+ resId: 1,
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_optional_columns_dropdown .dropdown-toggle").tabIndex,
+ -1
+ );
+ }
+ );
+
QUnit.test('one2many list edition, no "Remove" button in modal', async function (assert) {
serverData.models.partner.fields.foo.default = false;
diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js
index da3e8d4dc34..7ae2dc5ea12 100644
--- a/addons/web/static/tests/views/kanban_view_tests.js
+++ b/addons/web/static/tests/views/kanban_view_tests.js
@@ -6701,7 +6701,7 @@ QUnit.module("Views", (hooks) => {
await toggleColumnActions(0);
- assert.containsNone(target, '[tabindex]:not([tabindex="-1"])');
+ assert.containsN(target, ".o_kanban_record", 16);
assert.hasClass(document.activeElement, "o_searchview_input");
await triggerEvent(document.activeElement, null, "keydown", { key: "ArrowDown" });