Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

newrule(target_spacing_sufficient): WCAG 2.2: 2.5.8 Target size (minimum) #1719

Merged
merged 57 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e7bffa6
Merge pull request #1714 from IBMa/dev-1676
shunguoy Oct 12, 2023
8272bec
create initial rule #1674
shunguoy Oct 12, 2023
6fd5aa6
initial rule #1674
shunguoy Oct 13, 2023
0e811d3
initial rule #1674
shunguoy Oct 13, 2023
41d127d
initial rule #1674
shunguoy Oct 13, 2023
ee16f58
add utilities for target #1674
shunguoy Oct 16, 2023
5d1db0e
Merge branch 'master' into dev-1674
shunguoy Oct 17, 2023
dcb35ab
add more test cases #1674
shunguoy Oct 17, 2023
1157e8e
update rule and test cases #1674
shunguoy Oct 19, 2023
e9b4fe8
update test cases #1674
shunguoy Oct 19, 2023
a5e35b8
update the rule #1674
shunguoy Oct 23, 2023
7af7b56
New WCAG 2.2 help
philljenkins Oct 23, 2023
7a75e2f
Merge branch 'master' into dev-1674
shunguoy Oct 24, 2023
bd52e01
rule update #1674
shunguoy Oct 25, 2023
2b97681
remove trailing ")"
philljenkins Oct 25, 2023
d910f04
simplify table
philljenkins Oct 25, 2023
94d077d
Merge branch 'master' into dev-1674
shunguoy Oct 25, 2023
6977ba3
update the rule #1674
shunguoy Oct 25, 2023
f422915
expand technique title
philljenkins Oct 25, 2023
dd35095
update the rule #1674
shunguoy Oct 25, 2023
2b6839f
rule change #1674
shunguoy Oct 26, 2023
6055ee1
Inline added to help
philljenkins Oct 27, 2023
4ef2326
update the rule #1674
shunguoy Oct 30, 2023
ada4f8e
fix the bound issue #1674
shunguoy Oct 30, 2023
0f5ce36
Merge branch 'master' into dev-1674
philljenkins Oct 30, 2023
8082a50
Add rule set listings
philljenkins Oct 30, 2023
72f38be
Improve readability README-RULES.md
philljenkins Oct 31, 2023
c29fff8
update test cases and rule #1674
shunguoy Oct 31, 2023
4a3ed86
fix the test cases and rules #1674
shunguoy Nov 1, 2023
9f20424
Merge branch 'master' into dev-1674
shunguoy Nov 1, 2023
b81d424
clean up rule #1674
shunguoy Nov 1, 2023
33b8d1f
update the rule #1674
shunguoy Nov 1, 2023
7b22e46
update the rule #1674
shunguoy Nov 1, 2023
98503e2
update messages #1674
shunguoy Nov 1, 2023
c9a6840
change rule messages #1674
shunguoy Nov 1, 2023
7b9eeeb
Remove AAA
philljenkins Nov 2, 2023
4e62307
Merge branch 'master' into dev-1674
philljenkins Nov 2, 2023
dfff012
Update test.yml
shunguoy Nov 2, 2023
5704e27
update the test cases #1674
shunguoy Nov 2, 2023
44da63d
Doc Type added
philljenkins Nov 2, 2023
54a039a
Update .achecker.yml
shunguoy Nov 2, 2023
285d792
intent of requirement
philljenkins Nov 6, 2023
c465adc
do not vs don't
philljenkins Nov 6, 2023
dcc773b
cannot vs can't
philljenkins Nov 6, 2023
af64477
cleanup element_tabbable_unobscured.ts
shunguoy Nov 6, 2023
c73bad5
update bounds calculation #1674
shunguoy Nov 6, 2023
3b72fc8
cleanup the code #1674
shunguoy Nov 6, 2023
778027d
cleanup the code #1674
shunguoy Nov 6, 2023
363100e
editorial
tombrunet Nov 7, 2023
cb092c7
cleanup the code error #1674
shunguoy Nov 7, 2023
a1a263b
Merge branch 'master' into dev-1674
shunguoy Nov 8, 2023
93e38ec
comma
philljenkins Nov 8, 2023
ab13292
Update accessibility-checker-engine/help-v4/en-US/target_spacing_suff…
tombrunet Nov 8, 2023
3a7891f
Update accessibility-checker-engine/src/v4/rules/target_spacing_suffi…
tombrunet Nov 8, 2023
2fd96b8
Published URLs
philljenkins Nov 8, 2023
a7239f5
Merge branch 'master' into dev-1674
shunguoy Nov 9, 2023
c75a765
update the rule definition #1674
shunguoy Nov 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions accessibility-checker-engine/README-RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ Help integrates the following:
* Element location
* What to do
* Examples
* About the requirement
* Who does this affects?
* About this requirement
* Who does this affect?

Mappings of the latest rules to the standards, the individual failure messages, and `links to the Help files` are listed in the published [Checker rule sets](https://www.ibm.com/able/requirements/checker-rule-sets).
### Rule sets and Mappings

* Rule sets such as `IBM Accessibility v7.2`, `WCAG 2.2 (A & AA)`, etc., and mappings of the latest rules to the standards (Requirement and Rule IDs), the individual failure messages (by Reasons ID), and links to the Help files are listed in the published [Checker rule sets](https://www.ibm.com/able/requirements/checker-rule-sets)
* `npm run build:help` in the `.../accessibility-checker-engine` directory creates `dist/help/rules.html` that can be reviewed
* Each build creates the `Rules listing` artifact in **Actions** that can be reviewed prior to deployment.

## Test cases

Expand Down Expand Up @@ -143,16 +147,16 @@ Note: Rule changes are not automatically rebuilt. You will have to kill the rule

## Summary of steps to implement/update and test a new rule

* Create a rule id for a new rule.
* Create a rule id for a new rule, using the 3-word format with underscores: `type_property_test`.
* Create the help file in [help-v4](help-v4).
* Create the rule implementation in [src/v4/rules](src/v4/rules). The rule implementation includes the rule context, message, help, ruleset mappings, logic and outcome.
* Create the rule implementation in [src/v4/rules](src/v4/rules). The rule implementation includes the rule context, message, help, ruleset mappings, logic, and outcome.
* Create test cases for the rule in [test/v2/checker/accessibility/rules](test/v2/checker/accessibility/rules).
* Test the rules with the test cases. You may run the test cases locally, or run with the local rule server.

## Feedback and reporting bugs

If you think you've found a bug, have questions or suggestions, open a [GitHub Issue](https://github.com/IBMa/equal-access/issues). If you are an IBM employee, feel free to ask questions in the IBM internal Slack channel `#accessibility-at-ibm`.
If you think you've found a bug, or have questions or suggestions, open a [GitHub Issue](https://github.com/IBMa/equal-access/issues). If you are an IBM employee, feel free to ask questions in the IBM internal Slack channel `#accessibility-at-ibm`.

## License

[![IBM Equal Access Toolkit is released under the Apache-2.0 license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
[![IBM Equal Access Toolkit is released under the Apache-2.0 license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ <h3 id="ruleMessage"></h3>
* Using only the keyboard, make sure you can access all the functionality provided by the mouse event handlers.
* **And**, if there is no equivalent keyboard access, follow the event handler table below to provide the corresponding keyboard event handlers:

| Use | with |
| :------------ | :------------ |
| onmousedown | onkeydown |
| onmouseup | onkeyup |
| onclick | onkeypress |
| onmouseover | onfocus |
| onmouseout | onblur |
<!-- To add a table, use three or more hyphens (---) to create each column’s header, and use pipes (|) to separate each column, add a pipe on either end of the row. -->
<!-- Cell widths can vary, the rendered output should look the same. -->

| Use | with |
| -------------- | ------------- |
| onmousedown | onkeydown |
| onmouseup | onkeyup |
| onclick | onkeypress |
| onmouseover | onfocus |
| onmouseout | onblur |

</script></mark-down>
<!-- End main panel -->
Expand All @@ -74,12 +77,12 @@ <h3 id="ruleMessage"></h3>

### About this requirement

* [IBM 2.1.1 Keyboard](https://www.ibm.com/able/requirements/requirements/#2_1_1))
* [WCAG technique SCR20](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR20)
* [IBM 2.1.1 Keyboard](https://www.ibm.com/able/requirements/requirements/#2_1_1)
* [WCAG technique SCR20: Using both keyboard and other device-specific functions](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR20)

### Who does this affect?

* People using a screen reader, including blind, low vision and neurodivergent people
* People using a screen reader, including blind, low vision, and neurodivergent people
* People with low vision
* People with tremor or other movement disorders
* People who rely on keyboard control
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<!--
/******************************************************************************
Copyright:: 2022- IBM, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*****************************************************************************/
-->
<!-- Title and messages generated at build time -->
<link rel="icon" href="https://ibm.com/able/favicon-32x32.png" type="image/png">
<link rel="icon" href="https://ibm.com/able/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="../common/help.css" />
<script type="module">
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/code-snippet.min.js";
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/list.min.js";
</script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="../common/help.js"></script>
</head>
<body>
<div class="bx--grid toolHelp">
<div class="bx--row">
<div class="bx--col-sm-4 bx--col-md-8 bx--col-lg-16 toolHead">
<!-- Group message injected here -->
<h3 id="ruleMessage"></h3>
<!-- Severity level injected here -->
<div id="locLevel"></div>
<!-- Rule specific message injected here -->
<p id="groupLabel"></p>
</div>
</div>
<div class="bx--row">
<div class="bx--col-sm-4 bx--col-md-5 bx--col-lg-8 toolMain">
<!-- Start main panel -->
<mark-down><script type="text/plain">

### Why is this important?

Some people with physical impairments cannot click or touch small buttons that are close together, especially on mobile touch screens.

The aim is to provide adequate clearance from any adjacent elements to prevent accidental activation of adjacent elements.
And to provide a larger region of the display that will accept a touch or pointer action when elements overlap and in other situations where the target size may be smaller than that of the element.
philljenkins marked this conversation as resolved.
Show resolved Hide resolved

Even though exceptions such as “**_inline link text_**” are listed in [2.5.8 Target Size (Minimum AA)](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum), it is recommended to increase the size and spacing where possible.

**Note**: When a _minimum size_ can’t be met, then a _minimum spacing_ is required.
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
Therefore it is still possible to have small elements and meet the requirement, provided that the elements don't have any adjacent elements that are too close.
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
It is recommended to meet the minimum size requirement regardless of spacing.
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
For important links and controls, consider aiming for the stricter [2.5.5 Target Size (Enhanced AAA)](https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced) of at least 44 by 44 CSS pixels.

<!-- This is where the code snippet is injected -->
<div id="locSnippet"></div>

### What to do

- Increase the size of the element's target
- **Or**: increase the spacing around the target
- **Or**: ensure the function can be achieved through a different control on the same page that meets the minimum size and spacing requirement

</script></mark-down>
<!-- End main panel -->
<!-- This is where the rule id is injected -->
<div id="ruleInfo"></div>
</div>
<div class="bx--col-sm-4 bx--col-md-3 bx--col-lg-4 toolSide">
<!-- Start side panel -->
<mark-down><script type="text/plain">

### About this requirement

- [WCAG 2.5.8 Target Size (Minimum AA)](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum)
- [WCAG technique C42: Using min-height and min-width to ensure sufficient target spacing](https://www.w3.org/WAI/WCAG22/Techniques/css/C42)

### Who does this affect?

- People who use a mobile device where the touch screen is the primary mode of interaction
- People with tremors or other movement disorders using a mouse, stylus, or touch input
- People using a device in environments where they are exposed to shaking such as public transportation
- People with large fingers, or who are operating the device with only a part of their finger or knuckle
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
- People using a device with only one hand
- Many older adults or other users who have difficulty with fine motor movements

</script></mark-down>
<!-- End side panel -->
</div>
</div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { ARIAMapper } from "../../../aria/ARIAMapper";
import { DOMWalker } from "../../../dom/DOMWalker";
import { VisUtil } from "../../../dom/VisUtil";
import { FragmentUtil } from "./fragment";
import { getDefinedStyles } from "../../../../v4/util/CSSUtil";
import { getDefinedStyles, getComputedStyle } from "../../../../v4/util/CSSUtil";
import { DOMUtil } from "../../../dom/DOMUtil";
import { DOMMapper } from "../../../dom/DOMMapper";

export class RPTUtil {

Expand Down Expand Up @@ -419,6 +420,165 @@ export class RPTUtil {
}
}

/**
* a target is en element that accept a pointer action (click or touch)
*
*/
public static isTarget(element) {
if (!element) return false;

if (element.hasAttribute("tabindex") || RPTUtil.isTabbable(element)) return true;

const roles = RPTUtil.getRoles(element, true);
if (!roles && roles.length === 0)
return false;

let tagProperty = RPTUtil.getElementAriaProperty(element);
let allowedRoles = RPTUtil.getAllowedAriaRoles(element, tagProperty);
if (!allowedRoles && allowedRoles.length === 0)
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
return false;

let parent = element.parentElement;
// datalist, fieldset, optgroup, etc. may be just used for grouping purpose, so go up to the parent
while (parent && roles.some(role => role === 'group'))
parent = parent.parentElement;

if (parent && (parent.hasAttribute("tabindex") || RPTUtil.isTabbable(parent))) {
const target_roles =["listitem", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "switch", "treeitem"];
if (allowedRoles.includes('any') || roles.some(role => target_roles.includes(role)))
return true;
}
return false;
}

/**
* a target is en element that accept a pointer action (click or touch)
* a target is a browser default if it's a native widget (no user defined role) without user style
*/
public static isTargetBrowserDefault(element) {
if (!element) return false;

// user defained widget
const roles = RPTUtil.getRoles(element, false);
if (roles && roles.length > 0)
return false;

// no user style to space control size, including use of font
const styles = getDefinedStyles(element);
if (styles['line-height'] || styles['height'] || styles['width'] || styles['min-height'] || styles['min-width']
|| styles['font-size'] || styles['margin-top'] || styles['margin-bottom'] || styles['margin-left'] || styles['margin-right'])
return false;

return true;
}

/**
* an "inline" CSS display property tells the element to fit itself on the same line. An 'inline' element's width and height are ignored.
* some element has default inline property, such as <span>, <a>
* most formatting elements inherent inline property, such as <em>, <strong>, <i>, <small>
* other inline elements: <abbr> <acronym> <b> <bdo> <big> <br> <cite> <code> <dfn> <em> <i> <input> <kbd> <label>
* <map> <object> <output> <q> <samp> <script> <select> <small> <span> <strong> <sub> <sup> <textarea> <time> <tt> <var>
* an "inline-block" element still place element in the same line without breaking the line, but the element's width and height are applied.
* inline-block elements: img, button, select, meter, progress, marguee, also in Chrome: textarea, input
* A block-level element always starts on a new line, and the browsers automatically add some space (a margin) before and after the element.
* block-level elements: <address> <article> <aside> <blockquote> <canvas> <dd> <div> <dl> <dt> <fieldset> <figcaption> <figure> <footer> <form>
* <h1>-<h6> <header> <hr> <li> <main> <nav> <noscript> <ol> <p> <pre> <section> <table> <tfoot> <ul> <video>
*
* return: if it's inline element and { inline: true | false, text: true | false, violation: null | {node} }
*/
public static getInlineStatus(element) {
if (!element) return null;

const style = getComputedStyle(element);
if (!style) return null;

let status = { "inline": false, "text": false, "violation": null };
const udisplay = style.getPropertyValue("display");
// inline element only
if (udisplay !== 'inline')
return status;

status.inline = true;
const parent = element.parentElement;
if (parent) {
const mapper : DOMMapper = new DOMMapper();
const bounds = mapper.getBounds(element);
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
const style = getComputedStyle(parent);
const display = style.getPropertyValue("display");
// an inline element is inside a block. note <body> is a block element too
if (display === 'block' || display === 'inline-block') {
let containText = false;
// one or more inline elements with text in the same line: <target>, text<target>, <target>text, <inline>+text<target>, <target><inline>+text, text<target><inline>+
let walkNode = element.nextSibling;
let last = true;
while (walkNode) {
// note browsers insert Text nodes to represent whitespaces.
if (!containText && walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
// special case: <br> is styled 'inline' by default, but change the line
if (status.violation === null && walkNode.nodeName.toLowerCase() !== 'br') {
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display");
if (cDisplay === 'inline') {
last = false;
if (RPTUtil.isTarget(walkNode) && bounds.width < 24) {
// check if the horizontal spacing is sufficient
const bnds = mapper.getBounds(walkNode);
philljenkins marked this conversation as resolved.
Show resolved Hide resolved
if (Math.round(bounds.width/2) + bnds.left - (bounds.left + bounds.width) < 24)
status.violation = walkNode.nodeName.toLowerCase();
}
} else
break;
}
}
walkNode = walkNode.nextSibling;
}

walkNode = element.previousSibling;
let first = true;
let checked = false;
while (walkNode) {
// note browsers insert Text nodes to represent whitespaces.
if (!containText && walkNode.nodeType === Node.TEXT_NODE && walkNode.nodeValue && walkNode.nodeValue.trim().length > 0) {
containText = true;
} else if (walkNode.nodeType === Node.ELEMENT_NODE) {
// special case: <br> is styled 'inline' by default, but change the line
if (!checked && walkNode.nodeName.toLowerCase() !== 'br') {
const cStyle = getComputedStyle(walkNode);
const cDisplay = cStyle.getPropertyValue("display");
if (cDisplay === 'inline') {
first = false;
checked = true;
if (RPTUtil.isTarget(walkNode) && bounds.width < 24) {
// check if the horizontal spacing is sufficient
const bnds = mapper.getBounds(walkNode);
shunguoy marked this conversation as resolved.
Show resolved Hide resolved
if (Math.round(bounds.width/2) + bounds.left - (bnds.left + bnds.width) < 24) {
status.violation = status.violation === null ? walkNode.nodeName.toLowerCase() : status.violation + ", " + walkNode.nodeName.toLowerCase();
}
}
} else
break;
}
}
walkNode = walkNode.previousSibling;
}

// one or more inline elements are in the same line with text
if (containText)
status.text = true;

return status;
} else {
//parent is inline element
if (!RPTUtil.isInnerTextOnlyEmpty(parent))
status.text = true;
}
}
// all other cases
return status;
}

public static tabIndexLEZero(elem) {
if (RPTUtil.hasAttribute(elem, "tabindex")) {
if (elem.getAttribute("tabindex").match(/^-?\d+$/)) {
Expand Down
Loading