forked from angular-eslint/angular-eslint
/
mouse-events-have-key-events.ts
63 lines (57 loc) · 2.29 KB
/
mouse-events-have-key-events.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import type { TmplAstElement } from '@angular-eslint/bundled-angular-compiler';
import { getTemplateParserServices } from '@angular-eslint/utils';
import type { TSESLint } from '@typescript-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
import { getDomElements } from '../utils/get-dom-elements';
import { toPattern } from '../utils/to-pattern';
type Options = [];
export type MessageIds = 'mouseEventsHaveKeyEvents';
export const RULE_NAME = 'mouse-events-have-key-events';
const STYLE_GUIDE_LINK = 'https://www.w3.org/WAI/WCAG21/Understanding/keyboard';
const enum KeyEvents {
Blur = 'blur',
Focus = 'focus',
}
const enum MouseEvents {
MouseOut = 'mouseout',
MouseOver = 'mouseover',
}
export default createESLintRule<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'suggestion',
docs: {
description: `Ensures that the mouse events \`${MouseEvents.MouseOut}\` and \`${MouseEvents.MouseOver}\` are accompanied by \`${KeyEvents.Focus}\` and \`${KeyEvents.Blur}\` events respectively. Coding for the keyboard is important for users with physical disabilities who cannot use a mouse, AT compatibility, and screenreader users. See more at ${STYLE_GUIDE_LINK}`,
recommended: false,
},
schema: [],
messages: {
mouseEventsHaveKeyEvents: `\`{{mouseEvent}}\` must be accompanied by \`{{keyEvent}}\` for accessibility (${STYLE_GUIDE_LINK})`,
},
},
defaultOptions: [],
create(context) {
const parserServices = getTemplateParserServices(context);
const domElementsPattern = toPattern([...getDomElements()]);
const eventPairs = [
[KeyEvents.Blur, MouseEvents.MouseOut],
[KeyEvents.Focus, MouseEvents.MouseOver],
] as const;
return eventPairs.reduce<Record<string, TSESLint.RuleFunction>>(
(accumulator, [keyEvent, mouseEvent]) => ({
...accumulator,
[`Element$1[name=${domElementsPattern}]:has(BoundEvent[name='${mouseEvent}']):not(:has(BoundEvent[name='${keyEvent}']))`]({
sourceSpan,
}: TmplAstElement) {
const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
context.report({
loc,
messageId: 'mouseEventsHaveKeyEvents',
data: { keyEvent, mouseEvent },
});
},
}),
{},
);
},
});