Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions lib/rules/template-no-nested-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,23 @@ function isMenuItemNode(node) {
return getTextAttr(node, 'role') === 'menuitem';
}

function isSummaryFirstChildOfDetails(summaryNode, parentEntry) {
if (summaryNode.tag !== 'summary' || parentEntry.tag !== 'details') {
function isAllowedDetailsChild(childNode, parentEntry) {
if (parentEntry.tag !== 'details') {
return false;
}
const parentNode = parentEntry.node;
const children = parentNode.children || [];
// Non-<summary> children are flow content in the disclosed panel — allowed.
// <summary> is only allowed as the first non-whitespace child of <details>.
if (childNode.tag !== 'summary') {
return true;
}
const children = parentEntry.node.children || [];
const firstNonWhitespace = children.find((child) => {
if (child.type === 'GlimmerTextNode') {
return child.chars.trim().length > 0;
}
return true;
});
return firstNonWhitespace === summaryNode;
return firstNonWhitespace === childNode;
}

/** @type {import('eslint').Rule.RuleModule} */
Expand Down Expand Up @@ -208,8 +212,8 @@ module.exports = {
});
}
parentEntry.interactiveChildCount++;
} else if (isSummaryFirstChildOfDetails(node, parentEntry)) {
// <summary> as first non-whitespace child of <details> is allowed
} else if (isAllowedDetailsChild(node, parentEntry)) {
// flow content in the disclosed panel, or <summary> as first child
} else if (isMenuItemNode(parentEntry.node) && isMenuItemNode(node)) {
// Nested menuitem nodes are valid (menu/sub-menu pattern)
} else {
Expand Down
4 changes: 4 additions & 0 deletions tests/lib/rules/template-no-nested-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ ruleTester.run('template-no-nested-interactive', rule, {
},
'<template><details><summary>Details</summary>Something small enough to escape casual notice.</details></template>',
'<template><details> <summary>Details</summary>Something small enough to escape casual notice.</details></template>',
'<template><details><summary>Advanced</summary><label for="x">Field</label><input id="x" /></details></template>',
'<template><details><summary>More</summary><div><button type="button">Action</button></div></details></template>',
`<template>
<ul role="menubar" aria-label="functions" id="appmenu">
<li role="menuitem" aria-haspopup="true">
Expand Down Expand Up @@ -249,6 +251,8 @@ hbsRuleTester.run('template-no-nested-interactive', rule, {
'<label><input></label>',
'<details><summary>Details</summary>Something small enough to escape casual notice.</details>',
'<details> <summary>Details</summary>Something small enough to escape casual notice.</details>',
'<details><summary>Advanced</summary><label for="x">Field</label><input id="x" /></details>',
'<details><summary>More</summary><div><button type="button">Action</button></div></details>',
`
<ul role="menubar" aria-label="functions" id="appmenu">
<li role="menuitem" aria-haspopup="true">
Expand Down
Loading