From 390955b7512308e15e9045cd60724117bf99e908 Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Fri, 7 Oct 2022 10:38:36 +0000 Subject: [PATCH] [FIX] web: fix traceback when shift-tab on list field **Before** A traceback is raised when pressing shift-tab on the first column of the first row in a focused list field as shown in the vid: https://youtu.be/g97ZTp1QdBE **After** shift-tab on the first column of first row will now unselect the list field and immediately focuses on the previous field. **Additional change** We now allow skipping focus on the dropdown toggler by introducing new props. closes odoo/odoo#103485 X-original-commit: 9ba9977e42d201035b5299bc13ba03211cddbc1f Signed-off-by: Bruno Boi (boi) Signed-off-by: Joseph Caburnay (jcb) Co-authored-by: Bruno Boi --- .../web/static/src/core/dropdown/dropdown.js | 4 ++ .../web/static/src/core/dropdown/dropdown.xml | 1 + .../static/src/views/list/list_renderer.js | 1 + .../static/src/views/list/list_renderer.xml | 1 + .../web/static/tests/core/dropdown_tests.js | 2 +- addons/web/static/tests/helpers/utils.js | 8 ++- .../views/fields/one2many_field_tests.js | 62 +++++++++++++++++++ .../static/tests/views/kanban_view_tests.js | 2 +- 8 files changed, 77 insertions(+), 4 deletions(-) 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" });