stylelint-gamut 1.3.2 → 1.3.4
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 +7 -6
- package/package.json +20 -15
- package/src/README.md +7 -8
- package/src/index.js +100 -87
- package/src/utils.js +67 -65
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
# Rules
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- [`color-no-out-gamut-range`](./src/README.md): Throw warning if color goes out of sRGB color space and is not wrapped in `@media (color-gamut: p3) {}` or `@media (color-gamut: rec2020) {}`.
|
|
10
10
|
|
|
11
|
-
# Requirements
|
|
11
|
+
# Requirements
|
|
12
12
|
|
|
13
13
|
- **node version ≥ 12.0.0**
|
|
14
14
|
- **Stylelint version ≥ 14.0.0**.
|
|
@@ -33,22 +33,23 @@ Add `stylelint-gamut` to your Stylelint config `plugins` array, then add rules y
|
|
|
33
33
|
|
|
34
34
|
```json
|
|
35
35
|
{
|
|
36
|
-
"plugins": [
|
|
37
|
-
"stylelint-gamut"
|
|
38
|
-
],
|
|
36
|
+
"plugins": ["stylelint-gamut"],
|
|
39
37
|
"rules": {
|
|
40
|
-
"gamut/color-no-out-gamut-range": true
|
|
38
|
+
"gamut/color-no-out-gamut-range": true
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
# More
|
|
44
|
+
|
|
46
45
|
It is already used by the creator of [PostCSS] [Andrey Sitnik].
|
|
47
46
|
|
|
48
47
|
Read more about color spaces in css:
|
|
48
|
+
|
|
49
49
|
- [OKLCH in CSS: why we moved from RGB and HSL](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)
|
|
50
50
|
- [LCH colors in CSS: what, why, and how?](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/)
|
|
51
51
|
- [A Guide To Modern CSS Colors](https://www.smashingmagazine.com/2021/11/guide-modern-css-colors/)
|
|
52
|
+
- [Better dynamic themes in Tailwind with OKLCH color magic](https://evilmartians.com/chronicles/better-dynamic-themes-in-tailwind-with-oklch-color-magic)
|
|
52
53
|
|
|
53
54
|
[Stylelint]: https://stylelint.io/
|
|
54
55
|
[npm-version-img]: https://img.shields.io/npm/v/stylelint-gamut.svg
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stylelint-gamut",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Stylelint plugin for working with different color spaces",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"format": "prettier --write .",
|
|
8
|
+
"lint": "eslint --fix *.js",
|
|
7
9
|
"test": "jest",
|
|
8
10
|
"prepare": "husky install"
|
|
9
11
|
},
|
|
10
12
|
"lint-staged": {
|
|
11
|
-
"*.js": "eslint --fix"
|
|
13
|
+
"*.js": "eslint --fix",
|
|
14
|
+
".": "prettier -write"
|
|
12
15
|
},
|
|
13
16
|
"engines": {
|
|
14
17
|
"node": ">=16.0.0"
|
|
@@ -37,22 +40,24 @@
|
|
|
37
40
|
},
|
|
38
41
|
"homepage": "https://github.com/fpetrakov/stylelint-gamut#readme",
|
|
39
42
|
"peerDependencies": {
|
|
40
|
-
"stylelint": "^14.0.0 || ^15.0.0"
|
|
43
|
+
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
|
-
"eslint": "^8.
|
|
44
|
-
"eslint-config-
|
|
45
|
-
"eslint-
|
|
46
|
-
"eslint-plugin-
|
|
47
|
-
"eslint-plugin-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"jest
|
|
51
|
-
"jest-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
46
|
+
"eslint": "^8.43.0",
|
|
47
|
+
"eslint-config-prettier": "^9.0.0",
|
|
48
|
+
"eslint-config-standard": "^17.1.0",
|
|
49
|
+
"eslint-plugin-import": "^2.27.5",
|
|
50
|
+
"eslint-plugin-n": "^16.0.0",
|
|
51
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
52
|
+
"husky": "^8.0.3",
|
|
53
|
+
"jest": "^29.5.0",
|
|
54
|
+
"jest-cli": "^29.5.0",
|
|
55
|
+
"jest-preset-stylelint": "^6.1.0",
|
|
56
|
+
"lint-staged": "^15.0.2",
|
|
57
|
+
"prettier": "^3.0.3",
|
|
58
|
+
"stylelint": "^15.7.0"
|
|
54
59
|
},
|
|
55
60
|
"dependencies": {
|
|
56
|
-
"colorjs.io": "^0.4.
|
|
61
|
+
"colorjs.io": "^0.4.5"
|
|
57
62
|
}
|
|
58
63
|
}
|
package/src/README.md
CHANGED
|
@@ -4,9 +4,7 @@ Disallow out of gamut range colors.
|
|
|
4
4
|
|
|
5
5
|
<!-- prettier-ignore -->
|
|
6
6
|
```css
|
|
7
|
-
a { color: lch(48% 82 283) }
|
|
8
|
-
/** ↑
|
|
9
|
-
* This color is out of sRGB gamut range */
|
|
7
|
+
a { color: lch(48% 82 283) } /* This color is out of sRGB gamut range */
|
|
10
8
|
```
|
|
11
9
|
|
|
12
10
|
Colors declared using lch, oklch, lab and oklab functions can move from sRGB to P3 or Rec2020 color spaces, which are not supported by most of screens right now. It can happen by mistake. For instance, by converting colors from one space to another.
|
|
@@ -17,20 +15,18 @@ This rule checks if the color is in sRGB, p3 or rec2020 space. If it's not in sR
|
|
|
17
15
|
@media (color-gamut: p3) {
|
|
18
16
|
a {
|
|
19
17
|
color: lch(29.8% 42.5 109.485);
|
|
18
|
+
/* This color is in p3 gamut range */
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
/** ↑
|
|
23
|
-
* This color is in p3 gamut range */
|
|
24
21
|
```
|
|
25
22
|
|
|
26
23
|
```css
|
|
27
24
|
@media (color-gamut: rec2020) {
|
|
28
25
|
a {
|
|
29
26
|
color: lch(25.1% 42.5 109.485);
|
|
27
|
+
/* This color is in rec2020 gamut range */
|
|
30
28
|
}
|
|
31
29
|
}
|
|
32
|
-
/** ↑
|
|
33
|
-
* This color is in rec2020 gamut range */
|
|
34
30
|
```
|
|
35
31
|
|
|
36
32
|
In case of **css custom properties** you should wrap either the custom property declaration:
|
|
@@ -42,6 +38,7 @@ In case of **css custom properties** you should wrap either the custom property
|
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
40
|
```
|
|
41
|
+
|
|
45
42
|
or the rule that uses the custom property:
|
|
46
43
|
|
|
47
44
|
```css
|
|
@@ -55,7 +52,9 @@ or the rule that uses the custom property:
|
|
|
55
52
|
Custom properties inside color declaration are ignored:
|
|
56
53
|
|
|
57
54
|
```css
|
|
58
|
-
background-color: oklch(
|
|
55
|
+
background-color: oklch(
|
|
56
|
+
var(--oklch-primary) / var(--alpha-bg)
|
|
57
|
+
); /* not checked */
|
|
59
58
|
```
|
|
60
59
|
|
|
61
60
|
Please note that conditional imports are not checked, e.g.:
|
package/src/index.js
CHANGED
|
@@ -1,97 +1,110 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const stylelint = require("stylelint");
|
|
4
|
+
const Color = require("colorjs.io").default;
|
|
2
5
|
|
|
3
|
-
const stylelint = require('stylelint')
|
|
4
|
-
const Color = require('colorjs.io').default
|
|
5
6
|
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} = require(
|
|
7
|
+
isInColorGamutP3MediaQuery,
|
|
8
|
+
isStandardSyntaxProperty,
|
|
9
|
+
declarationValueIndex,
|
|
10
|
+
isInColorGamutRec2020MediaQuery,
|
|
11
|
+
startsWithNumber,
|
|
12
|
+
} = require("./utils");
|
|
12
13
|
|
|
13
|
-
const ruleName =
|
|
14
|
+
const ruleName = "gamut/color-no-out-gamut-range";
|
|
14
15
|
|
|
15
16
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
16
|
-
|
|
17
|
-
})
|
|
17
|
+
rejected: (color) => `Unexpected color out of gamut range "${color}"`,
|
|
18
|
+
});
|
|
18
19
|
|
|
19
20
|
const meta = {
|
|
20
|
-
|
|
21
|
-
}
|
|
21
|
+
url: "https://github.com/fpetrakov/stylelint-gamut/blob/master/src/README.md",
|
|
22
|
+
};
|
|
22
23
|
|
|
23
24
|
/** @type {import('stylelint').Rule} */
|
|
24
25
|
const ruleFunction = (primary) => {
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
26
|
+
return (root, result) => {
|
|
27
|
+
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
|
|
28
|
+
actual: primary,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!validOptions) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const customProperties = {};
|
|
36
|
+
|
|
37
|
+
root.walkDecls((decl) => {
|
|
38
|
+
if (!isStandardSyntaxProperty(decl.prop)) return;
|
|
39
|
+
|
|
40
|
+
const values = decl.value.match(
|
|
41
|
+
/(oklch|oklab|lab|lch|var)\([^)]+\)/g,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (!values) return;
|
|
45
|
+
|
|
46
|
+
// value is e.g. lch(48% 82 283 / 67%)
|
|
47
|
+
for (const value of values) {
|
|
48
|
+
check(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function check(value) {
|
|
52
|
+
const shouldBeIgnored =
|
|
53
|
+
!value.startsWith("var(--") &&
|
|
54
|
+
(value.includes("var(--") ||
|
|
55
|
+
!startsWithNumber(value.split("(")[1]));
|
|
56
|
+
|
|
57
|
+
if (shouldBeIgnored) return;
|
|
58
|
+
|
|
59
|
+
let customPropValue;
|
|
60
|
+
if (value.startsWith("var(--")) {
|
|
61
|
+
const varName = value.slice(4, -1);
|
|
62
|
+
if (
|
|
63
|
+
customProperties[varName] &&
|
|
64
|
+
!customProperties[varName].inQuery
|
|
65
|
+
) {
|
|
66
|
+
customPropValue = customProperties[varName].value;
|
|
67
|
+
} else {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const color = new Color(customPropValue || value);
|
|
73
|
+
|
|
74
|
+
if (color.inGamut("srgb")) return;
|
|
75
|
+
if (color.inGamut("p3") && isInColorGamutP3MediaQuery(decl))
|
|
76
|
+
return;
|
|
77
|
+
if (
|
|
78
|
+
color.inGamut("rec2020") &&
|
|
79
|
+
isInColorGamutRec2020MediaQuery(decl)
|
|
80
|
+
)
|
|
81
|
+
return;
|
|
82
|
+
|
|
83
|
+
if (decl.prop && decl.prop.startsWith("--")) {
|
|
84
|
+
customProperties[decl.prop] = {
|
|
85
|
+
value: decl.value.trim(),
|
|
86
|
+
inQuery: false,
|
|
87
|
+
};
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const index = declarationValueIndex(decl);
|
|
92
|
+
const endIndex = index + decl.value.length;
|
|
93
|
+
stylelint.utils.report({
|
|
94
|
+
message: messages.rejected(value),
|
|
95
|
+
node: decl,
|
|
96
|
+
index,
|
|
97
|
+
endIndex,
|
|
98
|
+
result,
|
|
99
|
+
ruleName,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
ruleFunction.ruleName = ruleName;
|
|
107
|
+
ruleFunction.messages = messages;
|
|
108
|
+
ruleFunction.meta = meta;
|
|
109
|
+
|
|
110
|
+
module.exports = stylelint.createPlugin(ruleName, ruleFunction);
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get the index of a declaration's value
|
|
@@ -6,23 +6,23 @@
|
|
|
6
6
|
* @param {import('postcss').Declaration} decl
|
|
7
7
|
* @returns {number}
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
9
|
+
const declarationValueIndex = (decl) => {
|
|
10
|
+
const raws = decl.raws;
|
|
11
|
+
|
|
12
|
+
return [
|
|
13
|
+
raws.prop && raws.prop.prefix,
|
|
14
|
+
(raws.prop && raws.prop.raw) || decl.prop,
|
|
15
|
+
raws.prop && raws.prop.suffix,
|
|
16
|
+
raws.between || ":",
|
|
17
|
+
raws.value && raws.value.prefix,
|
|
18
|
+
].reduce((count, str) => {
|
|
19
|
+
if (str) {
|
|
20
|
+
return count + str.length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return count;
|
|
24
|
+
}, 0);
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Check whether a property is standard
|
|
@@ -30,83 +30,85 @@ function declarationValueIndex (decl) {
|
|
|
30
30
|
* @param {string} property
|
|
31
31
|
* @return {boolean} If `true`, the property is standard
|
|
32
32
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const isStandardSyntaxProperty = (property) => {
|
|
34
|
+
// SCSS var (e.g. $var: x), list (e.g. $list: (x)) or map (e.g. $map: (key:value))
|
|
35
|
+
if (property.startsWith("$")) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
// Less var (e.g. @var: x)
|
|
40
|
+
if (property.startsWith("@")) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
// SCSS or Less interpolation
|
|
45
|
+
if (/#{.+?}|@{.+?}|\$\(.+?\)/.test(property)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
}
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* @param {import('postcss').Declaration} decl
|
|
54
54
|
* @returns {boolean}
|
|
55
55
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const isInColorGamutP3MediaQuery = (decl) => {
|
|
57
|
+
const parentMediaQuery = getMediaQueryParent(decl);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (
|
|
60
|
+
parentMediaQuery &&
|
|
61
|
+
/\(color-gamut:\s*p3\)/.test(parentMediaQuery.params)
|
|
62
|
+
) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
}
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
70
|
* @param {import('postcss').Declaration} decl
|
|
68
71
|
* @returns {boolean}
|
|
69
72
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const isInColorGamutRec2020MediaQuery = (decl) => {
|
|
74
|
+
const parentMediaQuery = getMediaQueryParent(decl);
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
if (
|
|
77
|
+
parentMediaQuery &&
|
|
78
|
+
/\(color-gamut:\s*rec2020\)/.test(parentMediaQuery.params)
|
|
79
|
+
) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
79
85
|
|
|
80
86
|
/**
|
|
81
87
|
* @param {import('postcss').Declaration} decl
|
|
82
88
|
* @returns {boolean | decl}
|
|
83
89
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
const getMediaQueryParent = (decl) => {
|
|
91
|
+
let parent = decl.parent;
|
|
86
92
|
|
|
87
|
-
|
|
93
|
+
if (parent && isMediaQuery(parent)) return parent;
|
|
88
94
|
|
|
89
|
-
|
|
95
|
+
parent = parent.parent;
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
}
|
|
97
|
+
return isMediaQuery(parent) && parent;
|
|
98
|
+
};
|
|
93
99
|
|
|
94
100
|
/**
|
|
95
101
|
* @param {import('postcss').Declaration} decl
|
|
96
102
|
* @returns {boolean}
|
|
97
103
|
*/
|
|
98
|
-
|
|
99
|
-
return decl.type === 'atrule' && decl.name === 'media'
|
|
100
|
-
}
|
|
104
|
+
const isMediaQuery = (decl) => decl.type === "atrule" && decl.name === "media";
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
return /^\d/.test(str)
|
|
104
|
-
}
|
|
106
|
+
const startsWithNumber = (str) => /^\d/.test(str);
|
|
105
107
|
|
|
106
108
|
module.exports = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
109
|
+
isStandardSyntaxProperty,
|
|
110
|
+
declarationValueIndex,
|
|
111
|
+
isInColorGamutP3MediaQuery,
|
|
112
|
+
isInColorGamutRec2020MediaQuery,
|
|
113
|
+
startsWithNumber,
|
|
114
|
+
};
|