commit 1e563824a211ab253715b1407e9161db30a818cd
parent 60a16946319aaacb77bb336ef35710640800138a
Author: Tim Giles <tgiles@mozilla.com>
Date: Mon, 15 Dec 2025 18:47:53 +0000
Bug 2002921 - Always remove tabindex when list items change. r=akulyk
When the last item in the list of moz-box-group is removed, we don't
remove the tabindex attribute of the list items. This means a keyboard
user cannot tab back to the list and interact with it. This patch fixes
that behavior by removing the tabindex attribute whenever the number of
list items change.
This commit adds tests to assert that the tabindex attribute is
correctly removed whenever the number of list items changes.
This patch also updates the List story so the last button can be removed
on click, or the space bar or enter key.
Differential Revision: https://phabricator.services.mozilla.com/D275138
Diffstat:
3 files changed, 93 insertions(+), 2 deletions(-)
diff --git a/toolkit/content/tests/widgets/test_moz_box_group.html b/toolkit/content/tests/widgets/test_moz_box_group.html
@@ -690,6 +690,85 @@
"footer is still in the forth position."
);
});
+
+ /*
+ Assert that the tabIndex attribute is removed from all list
+ items if the length of the list items changes. Otherwise,
+ there's a possibility that a user will not be able to keyboard
+ navigate back to the list due to the items having tabindex="-1"
+ **/
+ add_task(async function testWhenListItemsChange() {
+ const removeSelf = event => {
+ event.target.closest("moz-box-item").remove();
+ };
+ let listTemplate = html` <moz-button>Focus me!</moz-button>
+ <moz-box-group type="list">
+ <moz-box-item label="item 1">
+ <moz-button slot="actions" label="item 1 action 1"></moz-button>
+ </moz-box-item>
+ <moz-box-item label="item 2">
+ <moz-button slot="actions" label="item 2 action 1"></moz-button>
+ </moz-box-item>
+ <moz-box-item label="item 3">
+ <moz-button
+ slot="actions"
+ label="item 3 action 1"
+ @click=${removeSelf}
+ ></moz-button>
+ </moz-box-item>
+ </moz-box-group>`;
+ let {
+ children: [beforeButton, boxGroup],
+ } = await testHelpers.renderTemplate(listTemplate);
+
+ let list = boxGroup.shadowRoot.querySelector("ul");
+ ok(list, "Box group has a list element.");
+
+ // Focus the button outside of the list so we can easily
+ // move focus via the Tab key.
+ beforeButton.focus();
+ await synthesizeKey("KEY_Tab");
+ async function keyboardNavigate(direction) {
+ let keyCode = `KEY_Arrow${
+ direction.charAt(0).toUpperCase() + direction.slice(1)
+ }`;
+ synthesizeKey(keyCode);
+ await boxGroup.updateComplete;
+ }
+
+ // Move through the list until we reach the item that can be
+ // removed.
+ await keyboardNavigate("down");
+ await keyboardNavigate("down");
+ await keyboardNavigate("down");
+ is(
+ document.activeElement.label,
+ "item 3 action 1",
+ "The last action in the box group should be focused."
+ );
+ await synthesizeKey("KEY_Enter");
+
+ await boxGroup.updateComplete;
+ await Promise.all(boxGroup.listItems.map(item => item.updateComplete));
+
+ // Ensure the tabindex attribute has been removed from all the
+ // list items.
+ boxGroup.listItems.forEach(item => {
+ ok(
+ !item.getAttribute("tabindex"),
+ "List item should not have tab index due to an item being removed"
+ );
+ });
+
+ // Ensure that we can tab navigate back to the box group
+ beforeButton.focus();
+ await synthesizeKey("KEY_Tab");
+
+ ok(
+ boxGroup.contains(document.activeElement),
+ "Focus should be able to move back into the box group after a list item is removed"
+ );
+ });
</script>
</head>
<body>
diff --git a/toolkit/content/widgets/moz-box-group/moz-box-group.mjs b/toolkit/content/widgets/moz-box-group/moz-box-group.mjs
@@ -206,7 +206,11 @@ export default class MozBoxGroup extends MozLitElement {
"last",
i == this.listItems.length - 1 && !footerNode
);
+ item.removeAttribute("tabindex");
});
+ if (!this.#tabbable) {
+ this.#tabbable = true;
+ }
}
if (
diff --git a/toolkit/content/widgets/moz-box-group/moz-box-group.stories.mjs b/toolkit/content/widgets/moz-box-group/moz-box-group.stories.mjs
@@ -26,7 +26,7 @@ moz-box-item =
moz-box-button-1 =
.label = I'm a box button in a group
moz-box-button-2 =
- .label = I'm another box button in a group
+ .label = Delete this box button from a group
moz-box-link =
.label = I'm a box link in a group
moz-box-delete-action =
@@ -139,9 +139,17 @@ function basicElements() {
slot="actions-start"
></moz-button>
</moz-box-item>
- <moz-box-button data-l10n-id="moz-box-button-2"></moz-box-button>`;
+ <moz-box-button
+ iconsrc="chrome://global/skin/icons/delete.svg"
+ @click=${deleteItem}
+ data-l10n-id="moz-box-button-2"
+ ></moz-box-button> `;
}
+const deleteItem = event => {
+ event.target.remove();
+};
+
const appendItem = event => {
let group = event.target.getRootNode().querySelector("moz-box-group");