stylelint-plugin-defensive-css 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -39,6 +39,75 @@ configuration.
|
|
|
39
39
|
|
|
40
40
|
The plugin provides multiple rules that can be toggled on and off as needed.
|
|
41
41
|
|
|
42
|
+
[Accidental Hover](#accidental-hover) -
|
|
43
|
+
[Background-Repeat](#background-repeat) -
|
|
44
|
+
[Custom Property Fallbacks](#custom-property-fallbacks) -
|
|
45
|
+
[Flex Wrapping](#flex-wrapping) - [Scroll Chaining](#scroll-chaining) -
|
|
46
|
+
[Vendor Prefix Grouping](#vendor-prefix-grouping)
|
|
47
|
+
|
|
48
|
+
### Accidental Hover
|
|
49
|
+
|
|
50
|
+
> [Read more about this pattern in Defensive CSS](https://defensivecss.dev/tip/hover-media/)
|
|
51
|
+
|
|
52
|
+
We use hover effects to provide an indication to the user that an element is
|
|
53
|
+
clickable or active. That is fine for devices that have a mouse or a trackpad.
|
|
54
|
+
However, for mobile browsing hover effects can get confusing.
|
|
55
|
+
|
|
56
|
+
Enable this rule in order to prevent unintentional hover effects on mobile
|
|
57
|
+
devices.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"rules": {
|
|
62
|
+
"plugin/use-defensive-css": [true, { "accidental-hover": true }]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### ✅ Passing Examples
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
@media (hover: hover) {
|
|
71
|
+
.btn:hover {
|
|
72
|
+
color: black;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Will traverse nested media queries */
|
|
77
|
+
@media (hover: hover) {
|
|
78
|
+
@media (min-width: 1px) {
|
|
79
|
+
.btn:hover {
|
|
80
|
+
color: black;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Will traverse nested media queries */
|
|
86
|
+
@media (min-width: 1px) {
|
|
87
|
+
@media (hover: hover) {
|
|
88
|
+
@media (min-width: 100px) {
|
|
89
|
+
.btn:hover {
|
|
90
|
+
color: black;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### ❌ Failing Examples
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
.fail-btn:hover {
|
|
101
|
+
color: black;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (min-width: 1px) {
|
|
105
|
+
.fail-btn:hover {
|
|
106
|
+
color: black;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
42
111
|
### Background Repeat
|
|
43
112
|
|
|
44
113
|
> [Read more about this pattern in Defensive CSS](https://defensivecss.dev/tip/bg-repeat/)
|
package/package.json
CHANGED
|
@@ -5,6 +5,9 @@ const stylelint = require('stylelint');
|
|
|
5
5
|
const ruleName = 'plugin/use-defensive-css';
|
|
6
6
|
|
|
7
7
|
const ruleMessages = stylelint.utils.ruleMessages(ruleName, {
|
|
8
|
+
accidentalHover() {
|
|
9
|
+
return 'To prevent accidental hover states on mobile devices, wrap :hover selectors inside a @media (hover: hover) { ...your styles } query.';
|
|
10
|
+
},
|
|
8
11
|
backgroundRepeat() {
|
|
9
12
|
return 'Ensure a background-repeat property is defined when using a background image.';
|
|
10
13
|
},
|
|
@@ -30,6 +30,22 @@ let backgroundRepeatProps = { ...defaultBackgroundRepeatProps };
|
|
|
30
30
|
let flexWrappingProps = { ...defaultFlexWrappingProps };
|
|
31
31
|
let scrollChainingProps = { ...defaultScrollChainingProps };
|
|
32
32
|
let isLastStyleDeclaration = false;
|
|
33
|
+
let isWrappedInHoverAtRule = false;
|
|
34
|
+
|
|
35
|
+
function traverseParentRules(parent) {
|
|
36
|
+
console.log({ parent });
|
|
37
|
+
if (parent.parent.type === 'root') {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (parent.parent.type === 'atrule') {
|
|
42
|
+
if (parent.parent.params.includes('hover: hover')) {
|
|
43
|
+
isWrappedInHoverAtRule = true;
|
|
44
|
+
} else {
|
|
45
|
+
traverseParentRules(parent.parent);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
|
|
34
50
|
const ruleFunction = (_, options) => {
|
|
35
51
|
return (root, result) => {
|
|
@@ -44,6 +60,27 @@ const ruleFunction = (_, options) => {
|
|
|
44
60
|
JSON.stringify(decl) ===
|
|
45
61
|
JSON.stringify(decl.parent.nodes[decl.parent.nodes.length - 1]);
|
|
46
62
|
|
|
63
|
+
/* ACCIDENTAL HOVER */
|
|
64
|
+
if (options?.['accidental-hover']) {
|
|
65
|
+
const parent = decl.parent;
|
|
66
|
+
const selector = parent.selector;
|
|
67
|
+
const isHoverSelector = selector.includes(':hover');
|
|
68
|
+
isWrappedInHoverAtRule = false;
|
|
69
|
+
|
|
70
|
+
if (isHoverSelector) {
|
|
71
|
+
traverseParentRules(parent);
|
|
72
|
+
|
|
73
|
+
if (!isWrappedInHoverAtRule) {
|
|
74
|
+
stylelint.utils.report({
|
|
75
|
+
message: ruleMessages.accidentalHover(),
|
|
76
|
+
node: decl.parent,
|
|
77
|
+
result,
|
|
78
|
+
ruleName,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
/* BACKGROUND REPEAT */
|
|
48
85
|
if (options?.['background-repeat']) {
|
|
49
86
|
if (decl.prop === 'background' && decl.value.includes('url(')) {
|