stylelint-plugin-defensive-css 0.9.2 → 0.10.1
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
|
@@ -35,7 +35,8 @@ The plugin provides multiple rules that can be toggled on and off as needed.
|
|
|
35
35
|
3. [Custom Property Fallbacks](#custom-property-fallbacks)
|
|
36
36
|
4. [Flex Wrapping](#flex-wrapping)
|
|
37
37
|
5. [Scroll Chaining](#scroll-chaining)
|
|
38
|
-
6. [
|
|
38
|
+
6. [Scrollbar Gutter](#scrollbar-gutter)
|
|
39
|
+
7. [Vendor Prefix Grouping](#vendor-prefix-grouping)
|
|
39
40
|
|
|
40
41
|
---
|
|
41
42
|
|
|
@@ -328,6 +329,66 @@ div {
|
|
|
328
329
|
}
|
|
329
330
|
```
|
|
330
331
|
|
|
332
|
+
### Scrollbar Gutter
|
|
333
|
+
|
|
334
|
+
> [Read more about this pattern in Defensive CSS](https://defensivecss.dev/tip/scrollbar-gutter/)
|
|
335
|
+
|
|
336
|
+
Imagine a container with only a small amount of content with no need to scroll.
|
|
337
|
+
The content would be aligned evenly within the boundaries of its container. Now,
|
|
338
|
+
if that container has more content added, and a scrollbar appears, that
|
|
339
|
+
scrollbar will cause a layout shift, forcing the content to reflow and jump.
|
|
340
|
+
This behavior can be jarring.
|
|
341
|
+
|
|
342
|
+
To avoid layout shifting with variable content, enforce that a
|
|
343
|
+
`scrollbar-gutter` property is defined for any scrollable container.
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"rules": {
|
|
348
|
+
"plugin/use-defensive-css": [true, { "scrollbar-gutter": true }]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### ✅ Passing Examples
|
|
354
|
+
|
|
355
|
+
```css
|
|
356
|
+
div {
|
|
357
|
+
overflow-x: auto;
|
|
358
|
+
scrollbar-gutter: auto;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
div {
|
|
362
|
+
overflow: hidden scroll;
|
|
363
|
+
scrollbar-gutter: stable;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
div {
|
|
367
|
+
overflow: hidden; /* No scrollbar-gutter is needed in the case of hidden */
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
div {
|
|
371
|
+
overflow-block: auto;
|
|
372
|
+
scrollbar-gutter: stable both-edges;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### ❌ Failing Examples
|
|
377
|
+
|
|
378
|
+
```css
|
|
379
|
+
div {
|
|
380
|
+
overflow-x: auto;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
div {
|
|
384
|
+
overflow: hidden scroll;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
div {
|
|
388
|
+
overflow-block: auto;
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
331
392
|
### Vendor Prefix Grouping
|
|
332
393
|
|
|
333
394
|
> [Read more about this pattern in Defensive CSS](https://defensivecss.dev/tip/grouping-selectors/)
|
package/package.json
CHANGED
|
@@ -17,6 +17,9 @@ const ruleMessages = stylelint.utils.ruleMessages(ruleName, {
|
|
|
17
17
|
flexWrapping() {
|
|
18
18
|
return 'Flex rows must have a `flex-wrap` value defined.`';
|
|
19
19
|
},
|
|
20
|
+
scrollbarGutter() {
|
|
21
|
+
return `Containers with an auto or scroll 'overflow' must also have a 'scrollbar-gutter' property defined.`;
|
|
22
|
+
},
|
|
20
23
|
scrollChaining() {
|
|
21
24
|
return `Containers with an auto or scroll 'overflow' must also have an 'overscroll-behavior' property defined.`;
|
|
22
25
|
},
|
|
@@ -20,6 +20,11 @@ const defaultFlexWrappingProps = {
|
|
|
20
20
|
isMissingFlexWrap: true,
|
|
21
21
|
nodeToReport: undefined,
|
|
22
22
|
};
|
|
23
|
+
const defaultScrollbarGutterProps = {
|
|
24
|
+
hasOverflow: false,
|
|
25
|
+
hasScrollbarGutter: false,
|
|
26
|
+
nodeToReport: undefined,
|
|
27
|
+
};
|
|
23
28
|
const defaultScrollChainingProps = {
|
|
24
29
|
hasOverflow: false,
|
|
25
30
|
hasOverscrollBehavior: false,
|
|
@@ -28,10 +33,19 @@ const defaultScrollChainingProps = {
|
|
|
28
33
|
|
|
29
34
|
let backgroundRepeatProps = { ...defaultBackgroundRepeatProps };
|
|
30
35
|
let flexWrappingProps = { ...defaultFlexWrappingProps };
|
|
36
|
+
let scrollbarGutterProps = { ...defaultScrollbarGutterProps };
|
|
31
37
|
let scrollChainingProps = { ...defaultScrollChainingProps };
|
|
32
38
|
let isLastStyleDeclaration = false;
|
|
33
39
|
let isWrappedInHoverAtRule = false;
|
|
34
40
|
|
|
41
|
+
const overflowProperties = [
|
|
42
|
+
'overflow',
|
|
43
|
+
'overflow-x',
|
|
44
|
+
'overflow-y',
|
|
45
|
+
'overflow-inline',
|
|
46
|
+
'overflow-block',
|
|
47
|
+
];
|
|
48
|
+
|
|
35
49
|
function traverseParentRules(parent) {
|
|
36
50
|
if (parent.parent.type === 'root') {
|
|
37
51
|
return;
|
|
@@ -184,15 +198,39 @@ const ruleFunction = (_, options) => {
|
|
|
184
198
|
}
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
/* SCROLLBAR GUTTER */
|
|
202
|
+
if (options?.['scrollbar-gutter']) {
|
|
203
|
+
if (
|
|
204
|
+
overflowProperties.includes(decl.prop) &&
|
|
205
|
+
(decl.value.includes('auto') || decl.value.includes('scroll'))
|
|
206
|
+
) {
|
|
207
|
+
scrollbarGutterProps.hasOverflow = true;
|
|
208
|
+
scrollbarGutterProps.nodeToReport = decl;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (decl.prop.includes('scrollbar-gutter')) {
|
|
212
|
+
scrollbarGutterProps.hasScrollbarGutter = true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (isLastStyleDeclaration) {
|
|
216
|
+
if (
|
|
217
|
+
scrollbarGutterProps.hasOverflow &&
|
|
218
|
+
!scrollbarGutterProps.hasScrollbarGutter
|
|
219
|
+
) {
|
|
220
|
+
stylelint.utils.report({
|
|
221
|
+
message: ruleMessages.scrollbarGutter(),
|
|
222
|
+
node: scrollbarGutterProps.nodeToReport,
|
|
223
|
+
result,
|
|
224
|
+
ruleName,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
scrollbarGutterProps = { ...defaultScrollbarGutterProps };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
187
232
|
/* SCROLL CHAINING */
|
|
188
233
|
if (options?.['scroll-chaining']) {
|
|
189
|
-
const overflowProperties = [
|
|
190
|
-
'overflow',
|
|
191
|
-
'overflow-x',
|
|
192
|
-
'overflow-y',
|
|
193
|
-
'overflow-inline',
|
|
194
|
-
'overflow-block',
|
|
195
|
-
];
|
|
196
234
|
if (
|
|
197
235
|
overflowProperties.includes(decl.prop) &&
|
|
198
236
|
(decl.value.includes('auto') || decl.value.includes('scroll'))
|