Skip to content

Commit 5486047

Browse files
committed
Extract rule: template-no-down-event-binding
1 parent 472dbb0 commit 5486047

4 files changed

Lines changed: 207 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ rules in templates can be disabled with eslint directives with mustache or html
186186
| [template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md) | disallow aria-hidden on body element | | 🔧 | |
187187
| [template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md) | disallow ARIA roles, states, and properties on elements that do not support them | | | |
188188
| [template-no-autofocus-attribute](docs/rules/template-no-autofocus-attribute.md) | disallow autofocus attribute | | 🔧 | |
189+
| [template-no-down-event-binding](docs/rules/template-no-down-event-binding.md) | disallow mouse down event bindings | | | |
189190
| [template-no-empty-headings](docs/rules/template-no-empty-headings.md) | disallow empty heading elements | | | |
190191
| [template-no-heading-inside-button](docs/rules/template-no-heading-inside-button.md) | disallow heading elements inside button elements | | | |
191192
| [template-no-invalid-aria-attributes](docs/rules/template-no-invalid-aria-attributes.md) | disallow invalid aria-* attributes | | | |
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ember/template-no-down-event-binding
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallows mouse down and touch start event bindings.
6+
7+
Mouse down and touch start events can cause accessibility issues because they don't work well with keyboard navigation. Use `click` or `keydown` events instead.
8+
9+
## Rule Details
10+
11+
This rule disallows the use of `mousedown` and `touchstart` events in templates.
12+
13+
## Examples
14+
15+
Examples of **incorrect** code for this rule:
16+
17+
```gjs
18+
<template>
19+
<button {{on "mousedown" this.handleMouseDown}}>Click</button>
20+
</template>
21+
```
22+
23+
```gjs
24+
<template>
25+
<div {{on "touchstart" this.handleTouchStart}}>Content</div>
26+
</template>
27+
```
28+
29+
```gjs
30+
<template>
31+
<div onmousedown={{this.handleMouseDown}}>Content</div>
32+
</template>
33+
```
34+
35+
Examples of **correct** code for this rule:
36+
37+
```gjs
38+
<template>
39+
<button {{on "click" this.handleClick}}>Click</button>
40+
</template>
41+
```
42+
43+
```gjs
44+
<template>
45+
<button {{on "keydown" this.handleKeyDown}}>Press</button>
46+
</template>
47+
```
48+
49+
```gjs
50+
<template>
51+
<div {{on "mouseup" this.handleMouseUp}}>Content</div>
52+
</template>
53+
```
54+
55+
## Migration
56+
57+
Replace:
58+
59+
```gjs
60+
<button {{on "mousedown" this.action}}>
61+
```
62+
63+
With:
64+
65+
```gjs
66+
<button {{on "click" this.action}}>
67+
```
68+
69+
Or for keyboard support:
70+
71+
```gjs
72+
<button {{on "click" this.action}} {{on "keydown" this.handleKey}}>
73+
```
74+
75+
## References
76+
77+
- [eslint-plugin-ember template-no-down-event-binding](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-down-event-binding.md)
78+
- [MDN - Mouse events](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow mouse down event bindings',
7+
category: 'Accessibility',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-down-event-binding.md',
9+
templateMode: 'both',
10+
},
11+
fixable: null,
12+
schema: [],
13+
messages: {
14+
unexpected:
15+
'Avoid mousedown/touchstart events. Use click or keydown events instead for better accessibility.',
16+
},
17+
},
18+
19+
create(context) {
20+
const DOWN_EVENTS = new Set(['mousedown', 'touchstart']);
21+
22+
return {
23+
GlimmerElementNode(node) {
24+
// Check for onmousedown/ontouchstart attributes
25+
if (node.attributes) {
26+
for (const attr of node.attributes) {
27+
if (attr.name && DOWN_EVENTS.has(attr.name.replace('on', ''))) {
28+
context.report({
29+
node: attr,
30+
messageId: 'unexpected',
31+
});
32+
}
33+
}
34+
}
35+
36+
// Check for {{on "mousedown"}} or {{on "touchstart"}} modifiers
37+
if (node.modifiers) {
38+
for (const modifier of node.modifiers) {
39+
if (
40+
modifier.path?.type === 'GlimmerPathExpression' &&
41+
modifier.path.original === 'on' &&
42+
modifier.params?.length > 0
43+
) {
44+
const eventParam = modifier.params[0];
45+
if (eventParam.type === 'GlimmerStringLiteral' && DOWN_EVENTS.has(eventParam.value)) {
46+
context.report({
47+
node: modifier,
48+
messageId: 'unexpected',
49+
});
50+
}
51+
}
52+
}
53+
}
54+
},
55+
};
56+
},
57+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-down-event-binding');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
//------------------------------------------------------------------------------
9+
// Tests
10+
//------------------------------------------------------------------------------
11+
12+
const ruleTester = new RuleTester({
13+
parser: require.resolve('ember-eslint-parser'),
14+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
15+
});
16+
17+
ruleTester.run('template-no-down-event-binding', rule, {
18+
valid: [
19+
`<template>
20+
<button {{on "click" this.handleClick}}>Click</button>
21+
</template>`,
22+
`<template>
23+
<button {{on "keydown" this.handleKeyDown}}>Press</button>
24+
</template>`,
25+
`<template>
26+
<div {{on "mouseup" this.handleMouseUp}}>Content</div>
27+
</template>`,
28+
],
29+
30+
invalid: [
31+
{
32+
code: `<template>
33+
<button {{on "mousedown" this.handleMouseDown}}>Click</button>
34+
</template>`,
35+
output: null,
36+
errors: [
37+
{
38+
message:
39+
'Avoid mousedown/touchstart events. Use click or keydown events instead for better accessibility.',
40+
type: 'GlimmerElementModifierStatement',
41+
},
42+
],
43+
},
44+
{
45+
code: `<template>
46+
<div {{on "touchstart" this.handleTouchStart}}>Content</div>
47+
</template>`,
48+
output: null,
49+
errors: [
50+
{
51+
message:
52+
'Avoid mousedown/touchstart events. Use click or keydown events instead for better accessibility.',
53+
type: 'GlimmerElementModifierStatement',
54+
},
55+
],
56+
},
57+
{
58+
code: `<template>
59+
<div onmousedown={{this.handleMouseDown}}>Content</div>
60+
</template>`,
61+
output: null,
62+
errors: [
63+
{
64+
message:
65+
'Avoid mousedown/touchstart events. Use click or keydown events instead for better accessibility.',
66+
type: 'GlimmerAttrNode',
67+
},
68+
],
69+
},
70+
],
71+
});

0 commit comments

Comments
 (0)