From 3b9f9480502bb3b0fd6dc6653e75fcb065d5fc4a Mon Sep 17 00:00:00 2001 From: dsfx3d Date: Sun, 15 Aug 2021 04:58:33 +0530 Subject: [PATCH] check if click events are accompanied by key events Changes for rule, click-events-have-key-events Partly resolves #820 --- src/compiler/compile/compiler_warnings.ts | 6 +++++- src/compiler/compile/nodes/Element.ts | 15 +++++++++++++++ .../input.svelte | 13 +++++++++++++ .../warnings.json | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/a11y-click-events-have-key-events/input.svelte create mode 100644 test/validator/samples/a11y-click-events-have-key-events/warnings.json diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index bfcd779fc7..c2d63c2ad0 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -6,7 +6,7 @@ export default { custom_element_no_tag: { code: 'custom-element-no-tag', - message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ' + message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ' }, unused_export_let: (component: string, property: string) => ({ code: 'unused-export-let', @@ -132,6 +132,10 @@ export default { code: 'a11y-missing-content', message: `A11y: <${name}> element should have child content` }), + a11y_click_event_have_key_events: { + code: 'a11y-click-events-have-key-events', + message: 'A11y: on:click must be accompanied by at least one of the following: on:keyup, on:keydown, on:keypress' + }, redundant_event_modifier_for_touch: { code: 'redundant-event-modifier', message: 'Touch event handlers that don\'t use the \'event\' object are passive by default' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index dbde8f1b2d..1395522bc4 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -28,6 +28,9 @@ const aria_attribute_set = new Set(aria_attributes); const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic graphics-document graphics-object graphics-symbol grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' '); const aria_role_set = new Set(aria_roles); +const interactive_tags = 'a audio button details embed iframe img input keygen label menu object select textarea video'.split(' '); +const interactive_tag_set = new Set(interactive_tags); + const a11y_required_attributes = { a: ['href'], area: ['alt', 'aria-label', 'aria-labelledby'], @@ -516,6 +519,18 @@ export default class Element extends Node { if (handlers_map.has('mouseout') && !handlers_map.has('blur')) { component.warn(this, compiler_warnings.a11y_mouse_events_have_key_events('mouseout', 'blur')); } + + if (!interactive_tag_set.has(this.name)) { + const has_key_event_handlers = handlers_map.has('keyup') || handlers_map.has('keydown') || handlers_map.has('keypress'); + const is_aria_hidden = ( + attribute_map.has('aria-hidden') && + attribute_map.get('aria-hidden').get_static_value() === 'true' + ); + + if (handlers_map.has('click') && !has_key_event_handlers && !is_aria_hidden) { + component.warn(this, compiler_warnings.a11y_click_event_have_key_events); + } + } } validate_bindings_foreign() { diff --git a/test/validator/samples/a11y-click-events-have-key-events/input.svelte b/test/validator/samples/a11y-click-events-have-key-events/input.svelte new file mode 100644 index 0000000000..522a80df3b --- /dev/null +++ b/test/validator/samples/a11y-click-events-have-key-events/input.svelte @@ -0,0 +1,13 @@ + + +
{}} on:keydown={() => {}}>
+ +
{}} on:keyup={() => {}}>
+ +
{}} on:keypress={() => {}}>
+ + + +
{}} aria-hidden="true">
+ +
{}} /> diff --git a/test/validator/samples/a11y-click-events-have-key-events/warnings.json b/test/validator/samples/a11y-click-events-have-key-events/warnings.json new file mode 100644 index 0000000000..6915d3805f --- /dev/null +++ b/test/validator/samples/a11y-click-events-have-key-events/warnings.json @@ -0,0 +1,17 @@ +[ + { + "code": "a11y-click-events-have-key-events", + "end": { + "character": 301, + "column": 27, + "line": 13 + }, + "message": "A11y: on:click must be accompanied by at least one of the following: on:keyup, on:keydown, on:keypress", + "pos": 274, + "start": { + "character": 274, + "column": 0, + "line": 13 + } + } +]