Skip to content

Commit 6537068

Browse files
committed
refactor: align native-interactive-elements util with #37 canonical
Same alignment story as the is-native-element swap in the previous commit: copy #37's lib/utils/native-interactive-elements.js + test byte-for-byte so the two PRs can land in either order without conflict. Behavior change: <object> is no longer classified as interactive. axobject-query has no entry for <object>, and no authoritative source backs "interactive by default" for it — prior inclusion was based on a misattributed axobject-query citation. The <object tabindex="0"> valid-test is removed accordingly (it would now flag, which is the intended new behavior; invalid-case coverage for <object tabindex> belongs in a dedicated test once we decide how to frame the guidance).
1 parent 9742b2b commit 6537068

3 files changed

Lines changed: 23 additions & 23 deletions

File tree

lib/utils/native-interactive-elements.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,28 @@
44
* Native-interactive HTML element classification, shared across rules that need to
55
* ask "does this HTML tag natively expose interactive UI to keyboard / AT users?".
66
*
7-
* The set is hand-curated rather than derived from a single authority because
8-
* aria-query, axobject-query, HTML-AAM, WAI-ARIA, and browser reality disagree on
9-
* several rows. Decision rationale is documented per-tag:
7+
* Hand-curated rather than derived directly from axobject-query because
8+
* axobject-query disagrees with browser reality on several rows (notably
9+
* audio/video where axobject-query marks them unconditionally widget, but
10+
* browsers only render keyboard UI when `controls` is set). Decision
11+
* rationale is documented per-tag:
1012
*
11-
* | Element | Behavior | Rationale |
12-
* |-------------------------------------------------|----------------------|-----------|
13-
* | button, select, textarea, iframe, embed, | Interactive | aria-query/axobject-query widget + universally-accepted |
14-
* | summary, details | | |
15-
* | input (except type=hidden) | Interactive | Same as above, minus hidden |
16-
* | option, datalist | Interactive | aria-query roles option/listbox; axobject widget; HTML-AAM |
17-
* | a[href], area[href] | Interactive (cond.) | HTML-AAM: anchor interactivity requires href |
18-
* | audio[controls], video[controls] | Interactive | Browsers only render focusable UI with `controls` |
19-
* | audio, video (no controls) | NOT interactive | No keyboard semantics without controls; browsers agree |
20-
* | object | Interactive | axobject-query EmbeddedObjectRole |
21-
* | canvas | Interactive | axobject-query CanvasRole widget; bias toward no-FP |
22-
* | input[type=hidden] | NOT interactive | HTML spec: no UI, no focus, no AT exposure |
23-
* | menuitem | NOT interactive | Deprecated; no longer rendered in Chrome/Edge/Safari/FF |
24-
* | label | NOT interactive | axobject-query LabelRole is structure, not widget |
13+
* | Element | Behavior | Rationale |
14+
* |------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------|
15+
* | button, select, textarea, embed, summary | Interactive | axobject-query widget; universally accepted. |
16+
* | iframe | Interactive | axobject-query types it `window` (not widget), but iframe IS focusable and delegates focus. |
17+
* | details | Interactive | axobject-query types it `structure`, but <details> is a keyboard-activatable disclosure. |
18+
* | input (except type=hidden) | Interactive | axobject-query widget for every type except `hidden` (which has no entry). |
19+
* | option, datalist | Interactive | axobject-query widget (ListBoxOptionRole / ListBoxRole). |
20+
* | canvas | Interactive | axobject-query widget (CanvasRole); convention + no-false-positive bias. |
21+
* | a[href], area[href] | Interactive (cond.) | HTML-AAM: anchor interactivity requires href. (area has no axobject-query entry — pragmatic.) |
22+
* | audio[controls], video[controls] | Interactive | Stricter than axobject-query (which marks bare audio/video as widget). Browsers only render |
23+
* | | | keyboard-operable UI when `controls` is present. |
24+
* | audio, video (no controls) | NOT interactive | Matches browser behavior; axobject-query would disagree here. |
25+
* | input[type=hidden] | NOT interactive | HTML spec: no UI, no focus, no AT exposure. axobject-query has no entry. |
26+
* | menuitem | NOT interactive | Deprecated HTML; removed from all major browsers despite axobject-query still listing it. |
27+
* | label | NOT interactive | axobject-query LabelRole is structure, not widget. |
28+
* | object | NOT interactive | axobject-query has no entry for <object>; no authoritative source backs "interactive by default." |
2529
*/
2630

2731
// Unconditionally-interactive HTML tags (no attribute dependencies).
@@ -35,7 +39,6 @@ const UNCONDITIONAL_INTERACTIVE_TAGS = new Set([
3539
'details',
3640
'option',
3741
'datalist',
38-
'object',
3942
'canvas',
4043
]);
4144

tests/lib/rules/template-no-noninteractive-tabindex.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ ruleTester.run('template-no-noninteractive-tabindex', rule, {
2424
'<template><audio controls tabindex="0"></audio></template>',
2525
'<template><video controls tabindex="0"></video></template>',
2626

27-
// <object> is a widget per axobject-query — allow tabindex.
28-
'<template><object tabindex="0"></object></template>',
29-
3027
// Non-interactive element made interactive via role.
3128
'<template><div role="button" tabindex="0"></div></template>',
3229
'<template><div role="checkbox" tabindex="0" aria-checked="false"></div></template>',

tests/lib/utils/native-interactive-elements-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ describe('isNativeInteractive', () => {
101101
});
102102

103103
describe('<object>', () => {
104-
it('is interactive (axobject EmbeddedObjectRole)', () => {
105-
expect(isNativeInteractive(makeNode('object'), getTextAttrValue)).toBe(true);
104+
it('is NOT interactive (axobject-query has no entry for <object>)', () => {
105+
expect(isNativeInteractive(makeNode('object'), getTextAttrValue)).toBe(false);
106106
});
107107
});
108108

0 commit comments

Comments
 (0)