Skip to content

Commit af26fc3

Browse files
committed
Extract rule: template-no-down-event-binding
1 parent ffc4ad8 commit af26fc3

4 files changed

Lines changed: 211 additions & 3 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,10 @@ rules in templates can be disabled with eslint directives with mustache or html
176176

177177
### Accessibility
178178

179-
| Name | Description | 💼 | 🔧 | 💡 |
180-
| :--------------------------------------------------------------------------- | :-------------------------------------- | :- | :- | :- |
181-
| [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | | | |
179+
| Name | Description | 💼 | 🔧 | 💡 |
180+
| :----------------------------------------------------------------------------- | :-------------------------------------- | :- | :- | :- |
181+
| [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | | | |
182+
| [template-no-down-event-binding](docs/rules/template-no-down-event-binding.md) | disallow mouse down event bindings | | | |
182183

183184
### Best Practices
184185

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 no-down-event-binding](https://github.com/eslint-plugin-ember/eslint-plugin-ember/blob/master/docs/rule/no-down-event-binding.md)
78+
- [MDN - Mouse events](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
strictGjs: true,
9+
strictGts: true,
10+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-down-event-binding.md',
11+
},
12+
fixable: null,
13+
schema: [],
14+
messages: {
15+
unexpected:
16+
'Avoid mousedown/touchstart events. Use click or keydown events instead for better accessibility.',
17+
},
18+
},
19+
20+
create(context) {
21+
const DOWN_EVENTS = new Set(['mousedown', 'touchstart']);
22+
23+
return {
24+
GlimmerElementNode(node) {
25+
// Check for onmousedown/ontouchstart attributes
26+
if (node.attributes) {
27+
for (const attr of node.attributes) {
28+
if (attr.name && DOWN_EVENTS.has(attr.name.replace('on', ''))) {
29+
context.report({
30+
node: attr,
31+
messageId: 'unexpected',
32+
});
33+
}
34+
}
35+
}
36+
37+
// Check for {{on "mousedown"}} or {{on "touchstart"}} modifiers
38+
if (node.modifiers) {
39+
for (const modifier of node.modifiers) {
40+
if (
41+
modifier.path?.type === 'GlimmerPathExpression' &&
42+
modifier.path.original === 'on' &&
43+
modifier.params?.length > 0
44+
) {
45+
const eventParam = modifier.params[0];
46+
if (eventParam.type === 'GlimmerStringLiteral' && DOWN_EVENTS.has(eventParam.value)) {
47+
context.report({
48+
node: modifier,
49+
messageId: 'unexpected',
50+
});
51+
}
52+
}
53+
}
54+
}
55+
},
56+
};
57+
},
58+
};
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)