[FIX] web,*: Set dropdown button tags to span

This commit changes the arrow icon tags of autocomplete dropdowns to be
spans instead of an anchors.

This is because these elements are inert icons without href or handlers
attached, and they tricked the list keyboard navigation into considering
them as valid focusable elements.

X-original-commit: d253e41c44accd43cbd6478717e3978772076974
Part-of: odoo/odoo#122564
This commit is contained in:
Julien Mougenot
2023-05-26 11:40:45 +02:00
parent c997ac8e11
commit 772e7367fd
6 changed files with 98 additions and 5 deletions
@@ -6,7 +6,7 @@
<TagsList tags="this.tags"/>
<div t-if="!props.readonly" class="o_input_dropdown d-inline-flex w-100" tabindex="0" t-ref="mainElement" t-on-focus="onMainElementFocus" t-on-click="onMainElementFocus">
<span class="analytic_distribution_placeholder"/>
<a role="button" class="o_dropdown_button" draggable="false"/>
<span class="o_dropdown_button" />
<t t-call="analytic.AnalyticDistributionPopup"/>
</div>
</div>
@@ -17,7 +17,7 @@
autoSelect="props.autoSelect"
onSelect.bind="onSelect"
/>
<a role="button" class="o_dropdown_button" draggable="false" />
<span class="o_dropdown_button" />
</div>
</t>
</templates>
@@ -58,7 +58,7 @@
<input type="text" class="dropdown text-start w-100 o_input py-1"
t-att-id="getUniqueDomID('type')"
t-att-value="state.typeLabel" readonly=""/>
<a role="button" class="o_dropdown_button"/>
<span class="o_dropdown_button" />
</div>
</t>
<t t-foreach="availablePropertyTypes" t-as="option" t-key="option[0]">
@@ -16,7 +16,7 @@
sources="autocompleteSources"
onSelect.bind="({value}) => this.onOptionSelected(value)"
resetOnSelect="true"/>
<a role="button" class="o_dropdown_button" draggable="false" />
<span class="o_dropdown_button" />
</div>
</div>
</t>
@@ -55,7 +55,7 @@
dropdown="props.dropdown"
autofocus="props.autofocus"
/>
<a role="button" class="o_dropdown_button" draggable="false" />
<span class="o_dropdown_button" />
</div>
</t>
@@ -14405,6 +14405,99 @@ QUnit.module("Views", (hooks) => {
assert.strictEqual(document.activeElement, getDataRow(3).querySelector("[name=foo] input"));
});
QUnit.test("keyboard navigation with date range", async (assert) => {
serverData.models.foo.fields.date_end = { string: "Date End", type: "date" };
serverData.models.foo.records[0].date_end = "2017-01-26";
await makeView({
type: "list",
resModel: "foo",
serverData,
arch: `
<tree editable="bottom">
<field name="foo"/>
<field name="date" widget="daterange" options="{'end_date_field': 'date_end'}" />
<field name="int_field"/>
</tree>
`,
});
await click(target, ".o_data_row:first-child [name=foo]");
assert.strictEqual(
document.activeElement,
target.querySelector(".o_data_row:first-child [name=foo] input")
);
triggerHotkey("Tab");
await nextTick();
const [startDateInput, endDateInput] = target.querySelectorAll(
".o_data_row:first-child [name=date] input"
);
assert.strictEqual(document.activeElement, startDateInput);
triggerHotkey("Tab");
await nextTick();
assert.strictEqual(
document.activeElement,
startDateInput,
"programmatic tab shouldn't toggle focus"
);
await click(endDateInput);
assert.strictEqual(document.activeElement, endDateInput);
triggerHotkey("Tab");
await nextTick();
assert.strictEqual(
document.activeElement,
target.querySelector(".o_data_row:first-child [name=int_field] input")
);
});
QUnit.test("keyboard navigation with Many2One field", async (assert) => {
await makeView({
type: "list",
resModel: "foo",
serverData,
arch: `
<tree editable="bottom">
<field name="foo"/>
<field name="m2o"/>
<field name="int_field"/>
</tree>
`,
});
await click(target, ".o_data_row:first-child [name=foo]");
assert.strictEqual(
document.activeElement,
target.querySelector(".o_data_row:first-child [name=foo] input")
);
triggerHotkey("Tab");
await nextTick();
assert.strictEqual(
document.activeElement,
target.querySelector(".o_data_row:first-child [name=m2o] input")
);
triggerHotkey("Tab");
await nextTick();
assert.strictEqual(
document.activeElement,
target.querySelector(".o_data_row:first-child [name=int_field] input")
);
});
QUnit.test("multi-edit records with ENTER does not crash", async (assert) => {
serviceRegistry.add("error", errorService);