Skip to content

Commit c17ac2d

Browse files
committed
fix(template-no-nested-interactive): isMenuItemNode matches all three menu-item role variants (Copilot review)
1 parent b1e970d commit c17ac2d

2 files changed

Lines changed: 34 additions & 4 deletions

File tree

lib/rules/template-no-nested-interactive.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ function isCompositeWidgetPattern(parentRole, childRole) {
3838
}
3939

4040
function isMenuItemNode(node) {
41-
return getTextAttr(node, 'role') === 'menuitem';
41+
// Match all three menu-item role variants per ARIA taxonomy. `menuitem`,
42+
// `menuitemcheckbox`, and `menuitemradio` are all "menu items" — they can
43+
// carry submenus (via MENUITEM_ROLES in isCompositeWidgetPattern) and nest
44+
// each other (via the nested-menuitem compat exception). Keeping both
45+
// predicates symmetric avoids false positives on APG Menu Button /
46+
// Menubar patterns that mix the variants.
47+
return MENUITEM_ROLES.has(getTextAttr(node, 'role'));
4248
}
4349

4450
function isAllowedDetailsChild(childNode, parentEntry) {
@@ -225,9 +231,10 @@ module.exports = {
225231
// Derived from aria-query's requiredOwnedElements with superClass
226232
// inheritance — see lib/utils/interactive-roles.js.
227233
} else if (isMenuItemNode(parentEntry.node) && isMenuItemNode(node)) {
228-
// Nested menuitem nodes are valid (menu/sub-menu pattern) — kept
229-
// for historical compat since aria-query doesn't encode this via
230-
// requiredOwnedElements.
234+
// Nested menu-item nodes (any combination of menuitem /
235+
// menuitemcheckbox / menuitemradio) are valid — menu/sub-menu
236+
// pattern per WAI-ARIA APG. Kept for historical compat since
237+
// aria-query doesn't encode this via requiredOwnedElements.
231238
} else {
232239
context.report({
233240
node,

tests/lib/rules/template-no-nested-interactive.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,29 @@ ruleTester.run('template-no-nested-interactive', rule, {
7878
</li>
7979
</ul>
8080
</template>`,
81+
82+
// Mixed menu-item variants — `menuitemcheckbox` and `menuitemradio`
83+
// nested alongside plain `menuitem` follow the same APG Menu Button
84+
// pattern. Should not flag as nested-interactive.
85+
`<template>
86+
<ul role="menu" aria-label="options">
87+
<li role="menuitemcheckbox" aria-checked="false">Show hidden</li>
88+
<li role="menuitemradio" aria-checked="true">Sort by name</li>
89+
<li role="menuitemradio" aria-checked="false">Sort by date</li>
90+
</ul>
91+
</template>`,
92+
// Submenu attached to a menuitemcheckbox (APG permits this).
93+
`<template>
94+
<ul role="menu">
95+
<li role="menuitemcheckbox" aria-haspopup="true" aria-checked="false">
96+
Advanced
97+
<ul role="menu">
98+
<li role="menuitem">Inspect</li>
99+
<li role="menuitem">Export</li>
100+
</ul>
101+
</li>
102+
</ul>
103+
</template>`,
81104
`<template>
82105
<label> My input:
83106
{{#if @select}}

0 commit comments

Comments
 (0)