-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTouchTargetOverlapRule.kt
More file actions
53 lines (45 loc) · 2.23 KB
/
Copy pathTouchTargetOverlapRule.kt
File metadata and controls
53 lines (45 loc) · 2.23 KB
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
package com.composea11yscanner.rules
import com.composea11yscanner.core.model.A11yIssue
import com.composea11yscanner.core.model.A11yNode
import com.composea11yscanner.core.model.A11ySeverity
import com.composea11yscanner.core.model.Rect
import com.composea11yscanner.core.rule.BaseScanRule
/** Reports interactive nodes whose effective pointer target overlaps another target. */
class TouchTargetOverlapRule : BaseScanRule() {
override val ruleId = "touch-target-overlap"
override val ruleName = "Touch Target Overlap"
override val severity = A11ySeverity.Warning
override val wcagReference: String? = null
override fun evaluateAll(nodes: List<A11yNode>): List<A11yIssue> {
val targets = nodes.filter { node ->
node.isTouchTarget &&
!node.isMergedDescendant &&
node.effectiveTouchBounds?.isEmpty() == false
}
val overlapsByNodeId = mutableMapOf<String, MutableSet<String>>()
targets.forEachIndexed { index, first ->
for (secondIndex in index + 1 until targets.size) {
val second = targets[secondIndex]
if (!first.effectiveTouchBounds!!.overlaps(second.effectiveTouchBounds!!)) continue
overlapsByNodeId.getOrPut(first.nodeId) { mutableSetOf() }.add(second.nodeId)
overlapsByNodeId.getOrPut(second.nodeId) { mutableSetOf() }.add(first.nodeId)
}
}
return targets.mapNotNull { node ->
val overlappingIds = overlapsByNodeId[node.nodeId] ?: return@mapNotNull null
val targetWord = if (overlappingIds.size == 1) "target" else "targets"
issue(
node = node,
message = "Effective touch target overlaps ${overlappingIds.size} other $targetWord. " +
"Overlapping hit regions can make the intended control ambiguous.",
howToFix = "Increase spacing between controls, enlarge their layout bounds, or " +
"restructure the layout so effective touch regions do not overlap.",
)
}
}
}
private fun Rect.overlaps(other: Rect): Boolean =
left < other.right &&
right > other.left &&
top < other.bottom &&
bottom > other.top