Skip to content

refactor: derive INTERACTIVE_ROLES from aria-query taxonomy (+ widen menu-pattern exception)#27

Closed
johanrd wants to merge 10 commits intomasterfrom
refactor/interactive-roles-from-aria-query
Closed

refactor: derive INTERACTIVE_ROLES from aria-query taxonomy (+ widen menu-pattern exception)#27
johanrd wants to merge 10 commits intomasterfrom
refactor/interactive-roles-from-aria-query

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

Note

This is part of a series where Claude has audited eslint-plugin-ember against jsx-a11y, vuejs-accessibility, angular-eslint, lit-a11y and html-validate, ember-template-lint, and the HTML and WCAG specs.

Consolidates two hand-maintained INTERACTIVE_ROLES sets (in template-no-invalid-interactive and template-no-nested-interactive) into a single derivation sourced from aria-query's role taxonomy, and widens the composite-widget nesting exception so canonical WAI-ARIA APG patterns are not flagged.

Derivation

New lib/utils/interactive-roles.js — one shared set used by both rules:

const { roles } = require('aria-query');

function buildInteractiveRoleSet() {
  const result = new Set(['toolbar']);
  for (const [role, def] of roles) {
    if (def.abstract) continue;
    const descendsFromWidget = (def.superClass || []).some((chain) => chain.includes('widget'));
    if (descendsFromWidget) result.add(role);
  }
  return result;
}
  • All non-abstract roles with widget in any superClass chain per aria-query's taxonomy.
  • Plus explicit toolbar — it supports aria-activedescendant and is widget-like in practice but does not descend from widget in aria-query's data. Matches jsx-a11y's same manual addition.
  • tooltip is not included. Per WAI-ARIA 1.2 §5.3.3 "Document Structure Roles", tooltip is a document-structure role, not a widget; the spec says "The following roles describe structures that organize content in a page. Document structures are not usually interactive." aria-query confirms: tooltip.superClass is [['roletype', 'structure', 'section']] — no widget.

Matches jsx-a11y's isInteractiveRole derivation verbatim. (Note: lit-a11y uses the same derivation but explicitly excludes progressbar because its value is effectively readonly.)

Final set: 35 roles.

Composite-widget nesting exception

Previously template-no-nested-interactive hard-coded a single exception: menuitem-inside-menuitem. This PR widens it using a derived COMPOSITE_WIDGET_CHILDREN map driven by aria-query's requiredOwnedElements (a documented ARIA concept — WAI-ARIA 1.2 §5.2.6 Required Owned Elements), with superClass inheritance applied so treegrid inherits from both grid and tree.

Note: the superClass-based inheritance step is a composition of two spec facts (the superClass relation + requiredOwnedElements on ancestors), not a verbatim spec rule — it's a reasonable inference that matches APG patterns, but strictly speaking derived rather than directly cited.

Plus an explicit MENUITEM_ROLES → menu rule for the submenu direction (documented in APG's Menu pattern; aria-query does not encode this via requiredOwnedElements).

Relation to surrounding branches

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 13.28 ms 13.38 ms +0.7%
🟢 js medium 6.75 ms 6.45 ms -4.5%
js large 2.60 ms 2.58 ms -0.8%
gjs small 1.11 ms 1.11 ms +0.3%
gjs medium 554.44 µs 553.00 µs -0.3%
gjs large 220.43 µs 219.14 µs -0.6%
gts small 1.10 ms 1.10 ms +0.1%
gts medium 552.62 µs 552.22 µs -0.1%
gts large 218.21 µs 220.24 µs +0.9%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~2.76 GHz
cpu: AMD EPYC 9V74 80-Core Processor
runtime: node 24.15.0 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            16.53 ms/iter  18.57 ms █                    
                      (11.33 ms … 33.36 ms)  29.02 ms █ █                  
                    (  5.70 mb …  10.13 mb)   7.25 mb ███▃█▁▁█▃▃▃▃▃▁▁▁▁▃▆▁▆

js small (experiment)         13.97 ms/iter  14.52 ms    █▅                
                      (11.78 ms … 20.06 ms)  20.05 ms █▃███▃ ▃             
                    (  6.19 mb …   7.86 mb)   6.83 mb ████████▄▄▄██▁▄▁▁▁▁▁▄

                             ┌                                            ┐
                             ╷┌───────────┬────┐                          ╷
          js small (control) ├┤           │    ├──────────────────────────┤
                             ╵└───────────┴────┘                          ╵
                              ╷ ┌───┬┐             ╷
       js small (experiment)  ├─┤   │├─────────────┤
                              ╵ └───┴┘             ╵
                             └                                            ┘
                             11.33 ms           20.17 ms           29.02 ms

summary
  js small (experiment)
   1.18x faster than js small (control)

------------------------------------------- -------------------------------
js medium (control)            7.48 ms/iter   7.66 ms  █                   
                       (6.19 ms … 16.75 ms)  14.83 ms ▆█▄                  
                    (  2.66 mb …   4.61 mb)   3.54 mb ███▄▄▅▃▃▄▁▁▁▂▁▁▁▂▁▁▁▃

js medium (experiment)         7.25 ms/iter   7.76 ms  █                   
                       (6.05 ms … 14.02 ms)  12.57 ms  █                   
                    (  3.01 mb …   4.18 mb)   3.53 mb ▇█▆▃▂▅▂▃▄▂▁▂▁▁▂▃▂▁▁▁▂

                             ┌                                            ┐
                              ╷┌────┬┐                                    ╷
         js medium (control)  ├┤    │├────────────────────────────────────┤
                              ╵└────┴┘                                    ╵
                             ╷┌────┬──┐                       ╷
      js medium (experiment) ├┤    │  ├───────────────────────┤
                             ╵└────┴──┘                       ╵
                             └                                            ┘
                             6.05 ms           10.44 ms            14.83 ms

summary
  js medium (experiment)
   1.03x faster than js medium (control)

------------------------------------------- -------------------------------
js large (control)             3.10 ms/iter   2.92 ms  █                   
                       (2.17 ms … 11.47 ms)   8.49 ms ██▅                  
                    (241.26 kb …   3.11 mb)   1.44 mb ███▄▄▃▂▁▂▂▂▂▂▂▁▁▁▁▁▂▁

js large (experiment)          2.85 ms/iter   2.74 ms  █                   
                        (2.35 ms … 7.86 ms)   5.96 ms ▅█▃                  
                    (250.08 kb …   2.62 mb)   1.43 mb ███▂▂▂▃▂▂▂▁▁▁▁▁▁▁▁▁▁▂

                             ┌                                            ┐
                             ╷┌─────┬                                     ╷
          js large (control) ├┤     │─────────────────────────────────────┤
                             ╵└─────┴                                     ╵
                              ╷┌──┬                     ╷
       js large (experiment)  ├┤  │─────────────────────┤
                              ╵└──┴                     ╵
                             └                                            ┘
                             2.17 ms            5.33 ms             8.49 ms

summary
  js large (experiment)
   1.09x faster than js large (control)

------------------------------------------- -------------------------------
gjs small (control)            1.22 ms/iter   1.13 ms █                    
                        (1.08 ms … 5.90 ms)   5.61 ms █                    
                    (250.78 kb …   1.60 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.24 ms/iter   1.12 ms █                    
                        (1.08 ms … 6.43 ms)   5.64 ms █                    
                    (220.70 kb …   1.79 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
         gjs small (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌─┬                                          ╷
      gjs small (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             1.08 ms            3.36 ms             5.64 ms

summary
  gjs small (control)
   1.01x faster than gjs small (experiment)

------------------------------------------- -------------------------------
gjs medium (control)         610.91 µs/iter 565.83 µs █                    
                      (534.77 µs … 5.29 ms)   2.96 ms █                    
                    ( 97.90 kb …   1.04 mb) 541.23 kb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      599.82 µs/iter 559.37 µs █                    
                      (533.59 µs … 5.46 ms)   2.00 ms █                    
                    ( 74.82 kb …   1.24 mb) 540.72 kb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
        gjs medium (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                         ╷
     gjs medium (experiment) ││─────────────────────────┤
                             └┴                         ╵
                             └                                            ┘
                             533.59 µs           1.75 ms            2.96 ms

summary
  gjs medium (experiment)
   1.02x faster than gjs medium (control)

------------------------------------------- -------------------------------
gjs large (control)          257.97 µs/iter 229.98 µs █                    
                      (212.81 µs … 6.89 ms) 766.04 µs █                    
                    (  7.94 kb … 777.59 kb) 217.40 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       239.17 µs/iter 226.55 µs  █                   
                      (212.61 µs … 4.87 ms) 304.07 µs  █▃                  
                    ( 16.88 kb … 738.41 kb) 216.38 kb ▅██▅▇▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌───┬                                        ╷
         gjs large (control) │   │────────────────────────────────────────┤
                             └───┴                                        ╵
                             ┌─┬    ╷
      gjs large (experiment) │ │────┤
                             └─┴    ╵
                             └                                            ┘
                             212.61 µs         489.32 µs          766.04 µs

summary
  gjs large (experiment)
   1.08x faster than gjs large (control)

------------------------------------------- -------------------------------
gts small (control)            1.20 ms/iter   1.11 ms █                    
                        (1.07 ms … 5.74 ms)   5.35 ms █                    
                    (648.78 kb …   1.57 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.22 ms/iter   1.12 ms █                    
                        (1.07 ms … 6.28 ms)   5.38 ms █                    
                    (504.59 kb …   1.63 mb)   1.05 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
         gts small (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌─┬                                          ╷
      gts small (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             1.07 ms            3.23 ms             5.38 ms

summary
  gts small (control)
   1.02x faster than gts small (experiment)

------------------------------------------- -------------------------------
gts medium (control)         598.95 µs/iter 559.63 µs █                    
                      (533.92 µs … 5.11 ms)   1.54 ms █                    
                    ( 59.05 kb …   1.26 mb) 541.47 kb ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      594.89 µs/iter 559.75 µs █                    
                      (532.64 µs … 5.46 ms)   1.83 ms █                    
                    (  1.42 kb …   1.07 mb) 540.45 kb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                ╷
        gts medium (control) │ │────────────────────────────────┤
                             └─┴                                ╵
                             ┌─┬                                          ╷
     gts medium (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             532.64 µs           1.18 ms            1.83 ms

summary
  gts medium (experiment)
   1.01x faster than gts medium (control)

------------------------------------------- -------------------------------
gts large (control)          240.44 µs/iter 225.77 µs ██                   
                      (211.20 µs … 4.71 ms) 439.40 µs ██                   
                    ( 17.26 kb … 802.22 kb) 216.89 kb ███▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       249.00 µs/iter 230.51 µs  █                   
                      (212.29 µs … 5.01 ms) 409.08 µs ▆█▂                  
                    (183.84 kb … 776.98 kb) 216.62 kb ███▄▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌────┬                                      ╷
         gts large (control) ├┤    │──────────────────────────────────────┤
                             ╵└────┴                                      ╵
                             ╷┌─────┬                               ╷
      gts large (experiment) ├┤     │───────────────────────────────┤
                             ╵└─────┴                               ╵
                             └                                            ┘
                             211.20 µs         325.30 µs          439.40 µs

summary
  gts large (control)
   1.04x faster than gts large (experiment)

johanrd added a commit that referenced this pull request Apr 22, 2026
Import INTERACTIVE_ROLES and COMPOSITE_WIDGET_CHILDREN from the shared
lib/utils/interactive-roles.js util (introduced in #27 — byte-identical
copy here so either PR can land first without conflict). Drop the
hardcoded 19-role set previously duplicated inline in each rule.

Behavior changes:

- ARIA widget role set expands from 19 to 35 roles — picks up
  menubar, menu, listbox, tree, tablist, grid, treegrid, radiogroup,
  alertdialog, progressbar, and other widget-descended roles in
  aria-query's taxonomy that the hardcoded list missed.
- tooltip is no longer treated as interactive. Per WAI-ARIA 1.2 §5.3.3,
  tooltip is a document-structure role, not a widget. #27's util
  reflects this (tooltip explicitly excluded). Old <div role="tooltip"
  onclick> test moves from valid to invalid — spec-correct.
- Composite-widget nesting exception expanded via COMPOSITE_WIDGET_CHILDREN.
  Canonical APG patterns (<ul role="menubar"><li role="menuitem">...,
  <ul role="listbox"><li role="option">..., grid/row/gridcell, treegrid,
  radiogroup/radio) no longer flag as nested-interactive. Previously the
  rule only handled menuitem-in-menuitem explicitly.

Pairs with #37's HTML-content-model authority split to complete the
two-authority architecture: HTML §3.2.5.2.7 via html-interactive-content,
ARIA widget taxonomy via interactive-roles. Each util cites one authority
honestly; rules compose both.
@johanrd johanrd requested a review from Copilot April 22, 2026 10:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors accessibility role handling by centralizing INTERACTIVE_ROLES derivation from aria-query and expanding template-no-nested-interactive to allow canonical composite-widget role hierarchies (per requiredOwnedElements, plus an APG submenu exception).

Changes:

  • Introduce lib/utils/interactive-roles.js to derive INTERACTIVE_ROLES from aria-query (+ manual toolbar, exclude tooltip) and to compute COMPOSITE_WIDGET_CHILDREN.
  • Update template-no-invalid-interactive and template-no-nested-interactive to consume the shared role utilities.
  • Add/adjust tests to validate role derivation and composite-widget nesting exceptions (including tooltip now being treated as non-interactive).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/utils/interactive-roles.js New shared derivation for interactive roles and composite-widget child/descendant allowances.
lib/rules/template-no-nested-interactive.js Switch to derived role sets and broaden nesting exception to composite-widget patterns (+ submenu rule).
lib/rules/template-no-invalid-interactive.js Switch to derived interactive role set; tooltip no longer treated as interactive.
tests/lib/utils/interactive-roles-test.js New tests for INTERACTIVE_ROLES and COMPOSITE_WIDGET_CHILDREN derivation and drift detection.
tests/lib/rules/template-no-nested-interactive.js Add valid cases for canonical composite-widget hierarchies.
tests/lib/rules/template-no-invalid-interactive.js Remove tooltip from valid widget-role cases; add invalid case asserting tooltip is non-interactive.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils/interactive-roles.js
Comment thread lib/rules/template-no-nested-interactive.js Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rules/template-no-nested-interactive.js
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils/is-native-element.js
Comment thread tests/lib/utils/interactive-roles-test.js
Comment thread lib/rules/template-no-nested-interactive.js
johanrd added a commit that referenced this pull request Apr 24, 2026
@johanrd johanrd requested a review from Copilot April 24, 2026 13:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rules/template-no-invalid-interactive.js
Comment thread lib/utils/is-native-element.js
johanrd added a commit that referenced this pull request Apr 24, 2026
@johanrd johanrd force-pushed the refactor/interactive-roles-from-aria-query branch from dff1d91 to 9c49f26 Compare April 24, 2026 13:50
@johanrd johanrd requested a review from Copilot April 24, 2026 13:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils/is-native-element.js
@johanrd johanrd requested a review from Copilot April 24, 2026 17:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@johanrd johanrd force-pushed the refactor/interactive-roles-from-aria-query branch from 11787d4 to b7cfff0 Compare April 26, 2026 08:04
johanrd added a commit that referenced this pull request Apr 26, 2026
Import INTERACTIVE_ROLES and COMPOSITE_WIDGET_CHILDREN from the shared
lib/utils/interactive-roles.js util (introduced in #27 — byte-identical
copy here so either PR can land first without conflict). Drop the
hardcoded 19-role set previously duplicated inline in each rule.

Behavior changes:

- ARIA widget role set expands from 19 to 35 roles — picks up
  menubar, menu, listbox, tree, tablist, grid, treegrid, radiogroup,
  alertdialog, progressbar, and other widget-descended roles in
  aria-query's taxonomy that the hardcoded list missed.
- tooltip is no longer treated as interactive. Per WAI-ARIA 1.2 §5.3.3,
  tooltip is a document-structure role, not a widget. #27's util
  reflects this (tooltip explicitly excluded). Old <div role="tooltip"
  onclick> test moves from valid to invalid — spec-correct.
- Composite-widget nesting exception expanded via COMPOSITE_WIDGET_CHILDREN.
  Canonical APG patterns (<ul role="menubar"><li role="menuitem">...,
  <ul role="listbox"><li role="option">..., grid/row/gridcell, treegrid,
  radiogroup/radio) no longer flag as nested-interactive. Previously the
  rule only handled menuitem-in-menuitem explicitly.

Pairs with #37's HTML-content-model authority split to complete the
two-authority architecture: HTML §3.2.5.2.7 via html-interactive-content,
ARIA widget taxonomy via interactive-roles. Each util cites one authority
honestly; rules compose both.
@johanrd johanrd requested a review from Copilot April 26, 2026 08:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

johanrd added 10 commits April 27, 2026 16:19
Consolidates two hand-maintained INTERACTIVE_ROLES sets (in
template-no-invalid-interactive and template-no-nested-interactive)
into a single derivation sourced from aria-query's role taxonomy.

lib/utils/interactive-roles.js filters concrete roles whose superClass
chain includes widget / command / composite / input / range, then adds
tooltip and toolbar explicitly (same rationale as jsx-a11y: ARIA 1.2
categorizes tooltip as a widget but aria-query's superClass chain
doesn't reflect that; toolbar supports aria-activedescendant and is
widget-like in practice).

Behavioral change:
Adds 18 roles to the interactive set: columnheader, doc-backlink,
doc-biblioref, doc-glossref, doc-noteref, grid, listbox, menu, menubar,
meter, progressbar, radiogroup, row, rowheader, tablist, toolbar, tree,
treegrid. These were silently treated as non-interactive — a
composite widget (role="tree") could contain nested interactive
elements without being flagged; an <a href> inside a role="menu" was
accepted but nested-interactive per ARIA.

No removals — tooltip was in the hand list and is preserved explicitly.

Follow-up: template-no-nested-interactive's menu-pattern exception
broadened from "menuitem inside menuitem" to "menu patterns per
WAI-ARIA" — menuitem/menuitemcheckbox/menuitemradio inside
menu/menubar/menuitem, and menu inside menuitem (for submenus), are
the standard menu hierarchy.
Our earlier derivation was broader (widget|command|composite|input|range
ancestors). jsx-a11y and lit-a11y both use the narrower 'widget ancestor'
check. The only role our broader check added was 'meter' — spec-readonly
and not interactive in peer-plugin terms.

Narrow to match peer behavior exactly:
- derivation: def.superClass.some(chain => chain.includes('widget'))
- + 'toolbar' (doesn't descend from widget but supports activedescendant)
- + 'tooltip' (ARIA 1.2 categorization is ambiguous; our existing tests
  treat it as interactive)

No test changes — the new set is a subset of the previous except for
meter (not covered by any existing test).
Per WAI-ARIA 1.2 §5.3.3 — Document Structure Roles:
  https://www.w3.org/TR/wai-aria-1.2/#tooltip

Tooltip is explicitly listed under document-structure roles (alongside
img, note, paragraph, etc.), not widget roles. The spec notes:
'Document structures are not usually interactive.'

jsx-a11y and lit-a11y agree — neither adds tooltip to their interactive-
role set (both add only toolbar).

The one test case expecting tooltip to be treated as interactive moves
accordingly. aria-query's superClass for tooltip is structure/section,
which the narrow widget-ancestor filter correctly excludes.
…erns

Deriving INTERACTIVE_ROLES from aria-query's widget taxonomy added roles
(`listbox`, `tablist`, `tree`, `treegrid`, `grid`, `radiogroup`, `row`,
and their children) to `template-no-nested-interactive`'s interactive
set. Without a corresponding exception, canonical WAI-ARIA APG composite
widgets would trip the rule:

    <div role="listbox"><div role="option">A</div></div>
    <div role="tablist"><div role="tab">…</div></div>
    <div role="tree"><div role="treeitem">…</div></div>
    <div role="grid"><div role="row"><div role="gridcell">…</div></div></div>
    <div role="radiogroup"><div role="radio">…</div></div>

Widen the existing menu-pattern exception into a general composite-widget
exception driven by aria-query's `requiredOwnedElements` data. For each
parent role, inherit `requiredOwnedElements` from ancestor roles in
`superClass` (so `treegrid` picks up both `row` via `grid` and `treeitem`
via `tree`) and close transitively over intermediate composite roles (so
`grid → row → gridcell` is allowed at arbitrary depth in the hierarchy).
The submenu pattern `menuitem → menu`, which aria-query does not express
via `requiredOwnedElements`, is kept as an explicit special case.

Also add an invalid-interactive test for `role="tooltip"` — now that
tooltip is no longer in INTERACTIVE_ROLES (per WAI-ARIA 1.2 §5.3.3, it
is a Document Structure role), a click handler on a tooltip is an
invalid interactive handler on a non-interactive element.
Covers the full util surface:
- Canonical widget roles (button, link, combobox, etc.) — in set
- Composite-widget containers (listbox, grid, tablist, tree, treegrid) — in set
- toolbar manual override — in set (documented exception)
- tooltip exclusion — NOT in set (WAI-ARIA 1.2 §5.3.3)
- progressbar included (documented divergence from lit-a11y)
- Set-size pin (35 roles) — surfaces aria-query taxonomy drift as test failure
- COMPOSITE_WIDGET_CHILDREN sanity (listbox/option, grid/gridcell transitivity,
  treegrid/row+treeitem superClass inheritance, radiogroup/radio, submenu)

Also extend the util JSDoc to document progressbar inclusion vs lit-a11y's
readonly-value-based exclusion — previously only noted in the PR body.
…tails> body panel

During the rebase that introduced isSummaryFirstChildOfDetails, the
broader isAllowedDetailsChild check was lost. The disclosed panel of
<details> is flow content — interactive children after <summary> are
valid and must not be flagged. Restores isAllowedDetailsChild as a
wrapper that delegates the <summary>-position check to
isSummaryFirstChildOfDetails.
@johanrd johanrd force-pushed the refactor/interactive-roles-from-aria-query branch from 59b93ec to ebdc2c8 Compare April 27, 2026 14:22
@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented Apr 27, 2026

Closing in favour of test/composite-widget-nesting. The INTERACTIVE_ROLES derivation, COMPOSITE_WIDGET_CHILDREN map, and the util test are already on master (landed via piecemeal commits). The only missing piece — composite-widget hierarchy valid cases in the rule test — will be added via a clean branch from current master. The remaining diff on this PR reverts ember-cli#2748 (deletes html-interactive-content.js) and drops 'use strict', both of which would be regressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants