Skip to content

Commit 5657551

Browse files
committed
fix: treat <option> as inherently interactive
Add <option> and <datalist> to INHERENTLY_INTERACTIVE_TAGS in template-click-events-have-key-events. Both map to widget-descendant ARIA roles per aria-query's elementRoles (option -> option, datalist -> listbox), matching jsx-a11y's isInteractiveElement treatment. Previously, <option {{on "click" h}}> was reported as a false positive (confirmed via audit fixture B1). Updates audit/peer-parity.js to move <option> from DIVERGENCE to parity, and adds <datalist> alongside. <menuitem> is left out: the HTML element is deprecated and not present in aria-query's elementRoles, so its interactivity status is ambiguous and warrants separate discussion.
1 parent 5427f19 commit 5657551

3 files changed

Lines changed: 16 additions & 21 deletions

File tree

lib/rules/template-click-events-have-key-events.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ const { dom } = require('aria-query');
22

33
// Elements whose default HTML semantics make them interactive — a click handler
44
// here doesn't need a keyboard fallback because keyboard focus/activation is
5-
// already built in.
5+
// already built in. Derived from aria-query's `elementRoles` (tags whose
6+
// inherent ARIA role descends from `widget`); matches jsx-a11y's
7+
// `isInteractiveElement` treatment.
68
const INHERENTLY_INTERACTIVE_TAGS = new Set([
79
'button',
10+
'datalist',
811
'details',
912
'embed',
1013
'iframe',
1114
'input',
1215
'label',
16+
'option',
1317
'select',
1418
'summary',
1519
'textarea',

tests/audit/click-events-have-key-events/peer-parity.js

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ ruleTester.run('audit:click-events-have-key-events (gts)', rule, {
6868
'<template><button {{on "click" this.onClick}} class="foo"></button></template>',
6969
'<template><select {{on "click" this.onClick}} class="foo"></select></template>',
7070
'<template><textarea {{on "click" this.onClick}} class="foo"></textarea></template>',
71+
// <option> / <datalist> are interactive per aria-query elementRoles
72+
// (map to `option` and `listbox` widget roles respectively).
73+
// jsx-a11y / vue: valid.
74+
'<template><option {{on "click" this.onClick}} class="foo"></option></template>',
75+
'<template><datalist {{on "click" this.onClick}}></datalist></template>',
7176

7277
// <a> becomes interactive when it has href.
7378
// jsx-a11y / vue / angular / lit: valid.
@@ -103,12 +108,6 @@ ruleTester.run('audit:click-events-have-key-events (gts)', rule, {
103108
// looks at native HTML interactivity. So `<div role="button" {{on "click"}}>`
104109
// would be flagged by us. Captured below in DIVERGENCE.
105110

106-
// === DIVERGENCE — <option> treated as interactive by peers ===
107-
// jsx-a11y: valid (`<option onClick={...} className="foo" />`).
108-
// vue: valid.
109-
// aria-query includes <option> in `dom`, but our INHERENTLY_INTERACTIVE_TAGS
110-
// set does not — so we FLAG this. FALSE POSITIVE. See invalid block below.
111-
112111
// === DIVERGENCE — `<input type="hidden">` treated as valid by peers ===
113112
// jsx-a11y / vue: valid.
114113
// Our rule: `isInteractiveElement` returns false for `type="hidden"`, and
@@ -200,14 +199,6 @@ ruleTester.run('audit:click-events-have-key-events (gts)', rule, {
200199
errors: [{ messageId: 'needsKeyEvent' }],
201200
},
202201

203-
// === DIVERGENCE — <option> flagged (peers treat as valid) ===
204-
// jsx-a11y / vue: VALID. Our rule: INVALID.
205-
{
206-
code: '<template><option {{on "click" this.onClick}} class="foo"></option></template>',
207-
output: null,
208-
errors: [{ messageId: 'needsKeyEvent' }],
209-
},
210-
211202
// === DIVERGENCE — <input type="hidden"> flagged (peers treat as valid) ===
212203
// jsx-a11y / vue: VALID (hidden inputs don't need keyboard support).
213204
// Our rule: `isInteractiveElement` short-circuits to false for type=hidden,
@@ -304,6 +295,8 @@ hbsRuleTester.run('audit:click-events-have-key-events (hbs)', rule, {
304295
'<button {{on "click" this.a}}></button>',
305296
'<a href="/x" {{on "click" this.a}}></a>',
306297
'<input type="text" {{on "click" this.a}} />',
298+
'<option {{on "click" this.a}}></option>',
299+
'<datalist {{on "click" this.a}}></datalist>',
307300

308301
// Presentation / none.
309302
'<div role="presentation" {{on "click" this.a}}></div>',
@@ -339,12 +332,6 @@ hbsRuleTester.run('audit:click-events-have-key-events (hbs)', rule, {
339332
output: null,
340333
errors: [{ messageId: 'needsKeyEvent' }],
341334
},
342-
// DIVERGENCE — <option> flagged (peers treat as valid).
343-
{
344-
code: '<option {{on "click" this.a}}></option>',
345-
output: null,
346-
errors: [{ messageId: 'needsKeyEvent' }],
347-
},
348335
// DIVERGENCE — widget role (angular treats as interactive).
349336
{
350337
code: '<div role="button" {{on "click" this.a}}></div>',

tests/lib/rules/template-click-events-have-key-events.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ ruleTester.run('template-click-events-have-key-events', rule, {
1919
'<template><a href="/x" {{on "click" this.track}}>Link</a></template>',
2020
'<template><input type="checkbox" {{on "click" this.toggle}} /></template>',
2121
'<template><summary {{on "click" this.noop}}>More</summary></template>',
22+
'<template><option {{on "click" this.h}}>Foo</option></template>',
23+
'<template><datalist {{on "click" this.h}}></datalist></template>',
2224

2325
// Hidden from AT.
2426
'<template><div aria-hidden="true" {{on "click" this.noop}}></div></template>',
@@ -87,6 +89,8 @@ hbsRuleTester.run('template-click-events-have-key-events', rule, {
8789
'<div></div>',
8890
'<button {{on "click" this.toggle}}>Toggle</button>',
8991
'<a href="/x" {{on "click" this.track}}>Link</a>',
92+
'<option {{on "click" this.h}}>Foo</option>',
93+
'<datalist {{on "click" this.h}}></datalist>',
9094
'<div role="presentation" {{on "click" this.noop}}></div>',
9195
'<div aria-hidden="true" {{on "click" this.noop}}></div>',
9296
'<div {{on "click" this.onClick}} {{on "keydown" this.onKey}}></div>',

0 commit comments

Comments
 (0)