@domandigital/craft 0.3.0 → 0.4.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/CHANGELOG.md +37 -0
- package/STANDARD.md +28 -7
- package/dist/index.cjs +110 -17
- package/dist/index.js +110 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @domandigital/craft
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1b2e8bf: `checkRestraint` resolves token references instead of skipping them.
|
|
8
|
+
|
|
9
|
+
The old rule skipped any value starting with `var(`, on the reasoning that a
|
|
10
|
+
token reference is the token layer doing its job rather than a new value. True
|
|
11
|
+
of a hand-written sheet. On a Tailwind v4 sheet it is close to measuring
|
|
12
|
+
nothing: `text-sm` compiles to `font-size: var(--text-sm)` and `rounded-lg` to
|
|
13
|
+
`border-radius: var(--radius-lg)`, and `shadow-[…]` emits no `box-shadow` at
|
|
14
|
+
all — it assigns `--tw-shadow` and lets one shared composite read it.
|
|
15
|
+
|
|
16
|
+
Measured on the estate this was hiding, per surface: 19 font sizes reported
|
|
17
|
+
against 28 shipped, 5 shadows against 18, 7 radii against 13. A second surface
|
|
18
|
+
reported `ok: true` on every axis and is over budget on four.
|
|
19
|
+
|
|
20
|
+
What changes:
|
|
21
|
+
|
|
22
|
+
- A value that is exactly one `var()` is resolved through the token chain to
|
|
23
|
+
the value that renders, and counted.
|
|
24
|
+
- `--tw-shadow` assignments count as shadows; Tailwind's composite plumbing
|
|
25
|
+
value does not, and neither does its `0 0 #0000` initial.
|
|
26
|
+
- `var(--tw-shadow-color, X)` is unwrapped to `X`, so the report names what a
|
|
27
|
+
reader would see.
|
|
28
|
+
- Definitions are read from the whole sheet, including ignored layers, while
|
|
29
|
+
usage is still read only from kept ones. The layer exclusion is unchanged in
|
|
30
|
+
intent and now works: a framework default nothing uses still counts for
|
|
31
|
+
nothing. Used versus unused was always the real distinction; which layer the
|
|
32
|
+
definition sat in never was.
|
|
33
|
+
- New `unresolved-token` warning for a declaration whose token is defined
|
|
34
|
+
nowhere and which carries no fallback. It applies nothing, so it is not
|
|
35
|
+
vocabulary — it is a bug, and it now says so. It found two on first contact.
|
|
36
|
+
|
|
37
|
+
**Every consumer's baseline will move**, upward, on surfaces built with a
|
|
38
|
+
utility framework. That is the correction, not a regression.
|
|
39
|
+
|
|
3
40
|
## 0.3.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/STANDARD.md
CHANGED
|
@@ -117,13 +117,34 @@ Differentiation guards pass anything that differs from its siblings, however
|
|
|
117
117
|
ugly. What they cannot see is accumulation — eighteen font sizes, six shadows,
|
|
118
118
|
four competing accents, each added reasonably, together reading as noise.
|
|
119
119
|
|
|
120
|
-
The budget counts the vocabulary a surface **chose
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
The budget counts the vocabulary a surface **chose**, and the whole difficulty
|
|
121
|
+
is telling that apart from what the framework brought.
|
|
122
|
+
|
|
123
|
+
The first rule drew the line at the layer: `@layer theme` and `@layer base` were
|
|
124
|
+
excluded, because counting them reported a surface as over budget for values its
|
|
125
|
+
author never wrote. Right about the noise, wrong about the boundary. Tailwind v4
|
|
126
|
+
compiles `text-sm` to `font-size: var(--text-sm)` and `rounded-lg` to
|
|
127
|
+
`border-radius: var(--radius-lg)`, so with the definitions excluded *and* token
|
|
128
|
+
references skipped, a surface using `text-sm` seven hundred times reported it
|
|
129
|
+
zero times. One estate measured 19 font sizes and shipped 28; 5 shadows and
|
|
130
|
+
shipped 18. A count wrong in the under-reporting direction is worse than the
|
|
131
|
+
over-reporting kind it replaced: the first buries real findings in noise, the
|
|
132
|
+
second reports a pass.
|
|
133
|
+
|
|
134
|
+
The line is **used versus unused**, which is not a property of the layer:
|
|
135
|
+
|
|
136
|
+
- References are **resolved**, not skipped — through the token chain, to the
|
|
137
|
+
value that renders.
|
|
138
|
+
- A definition is only ever reached from a declaration in a kept layer, so a
|
|
139
|
+
framework default nothing uses still counts for nothing.
|
|
140
|
+
- `--tw-shadow` assignments count as shadows. `shadow-[…]` emits no literal
|
|
141
|
+
`box-shadow` at all; it assigns that property and lets one shared composite
|
|
142
|
+
read it.
|
|
143
|
+
- A reference that resolves to nothing is **not** vocabulary. It is a
|
|
144
|
+
declaration that silently applies nothing, and it is reported as its own
|
|
145
|
+
warning.
|
|
146
|
+
|
|
147
|
+
*Source: measured against the estate. The budget is the observed ceiling of the surfaces that read as designed; the resolution rule is what it took to measure them at all.*
|
|
127
148
|
|
|
128
149
|
## 10. What is deliberately not a rule
|
|
129
150
|
|
package/dist/index.cjs
CHANGED
|
@@ -852,10 +852,19 @@ function stripLayers(css, ignored) {
|
|
|
852
852
|
}
|
|
853
853
|
return out;
|
|
854
854
|
}
|
|
855
|
-
var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
855
|
+
var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
856
|
+
"inherit",
|
|
857
|
+
"initial",
|
|
858
|
+
"unset",
|
|
859
|
+
"revert",
|
|
860
|
+
"revert-layer"
|
|
861
|
+
]);
|
|
856
862
|
function atRuleBodies(css, prelude) {
|
|
857
863
|
const out = [];
|
|
858
|
-
const pattern = new RegExp(
|
|
864
|
+
const pattern = new RegExp(
|
|
865
|
+
prelude.source,
|
|
866
|
+
prelude.flags.includes("g") ? prelude.flags : `${prelude.flags}g`
|
|
867
|
+
);
|
|
859
868
|
let match;
|
|
860
869
|
while ((match = pattern.exec(css)) !== null) {
|
|
861
870
|
const start = match.index + match[0].length;
|
|
@@ -871,13 +880,64 @@ function atRuleBodies(css, prelude) {
|
|
|
871
880
|
}
|
|
872
881
|
return out;
|
|
873
882
|
}
|
|
874
|
-
function
|
|
883
|
+
function customProperties(css) {
|
|
884
|
+
const out = /* @__PURE__ */ new Map();
|
|
885
|
+
for (const match of css.matchAll(/(--[\w-]+)\s*:\s*([^;}]+)/g)) {
|
|
886
|
+
out.set(match[1].toLowerCase(), match[2].trim().replace(/\s+/g, " "));
|
|
887
|
+
}
|
|
888
|
+
return out;
|
|
889
|
+
}
|
|
890
|
+
function unwrapShadowColor(value) {
|
|
891
|
+
return value.replace(
|
|
892
|
+
/var\(\s*--tw-shadow-color\s*,\s*([^)]*(?:\([^)]*\))?[^)]*)\)/g,
|
|
893
|
+
"$1"
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
var SINGLE_VAR = /^var\(\s*(--[\w-]+)\s*(?:,([\s\S]*))?\)$/;
|
|
897
|
+
function resolveValue(value, props, depth = 0) {
|
|
898
|
+
if (depth > 8) return null;
|
|
899
|
+
const match = SINGLE_VAR.exec(value.trim());
|
|
900
|
+
if (match === null) return value.trim();
|
|
901
|
+
const target = props.get(match[1].toLowerCase());
|
|
902
|
+
if (target !== void 0 && target !== "initial")
|
|
903
|
+
return resolveValue(target, props, depth + 1);
|
|
904
|
+
const fallback = match[2]?.trim();
|
|
905
|
+
return fallback === void 0 || fallback === "" ? null : resolveValue(fallback, props, depth + 1);
|
|
906
|
+
}
|
|
907
|
+
function isVarOnlyList(value) {
|
|
908
|
+
const parts = value.split(",");
|
|
909
|
+
return parts.length > 1 && parts.every((part) => /^\s*var\(\s*--[\w-]+\s*\)\s*$/.test(part));
|
|
910
|
+
}
|
|
911
|
+
function distinctValues(css, property, props, unresolved) {
|
|
875
912
|
const pattern = new RegExp(`(?:^|[;{\\s])${property}\\s*:\\s*([^;}]+)`, "gi");
|
|
876
913
|
const seen = /* @__PURE__ */ new Set();
|
|
877
914
|
for (const match of css.matchAll(pattern)) {
|
|
878
|
-
const
|
|
879
|
-
if (
|
|
880
|
-
|
|
915
|
+
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
916
|
+
if (CSS_WIDE_KEYWORDS.has(raw)) continue;
|
|
917
|
+
const value = unwrapShadowColor(raw);
|
|
918
|
+
if (isVarOnlyList(value)) continue;
|
|
919
|
+
if (!SINGLE_VAR.test(value)) {
|
|
920
|
+
seen.add(value);
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (props === void 0) continue;
|
|
924
|
+
const resolved = resolveValue(value, props);
|
|
925
|
+
if (resolved === null) {
|
|
926
|
+
unresolved?.add(`${property}: ${value}`);
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
seen.add(unwrapShadowColor(resolved).toLowerCase());
|
|
930
|
+
}
|
|
931
|
+
return [...seen];
|
|
932
|
+
}
|
|
933
|
+
function twShadowValues(css, props) {
|
|
934
|
+
const seen = /* @__PURE__ */ new Set();
|
|
935
|
+
for (const match of css.matchAll(/--tw-shadow\s*:\s*([^;}]+)/g)) {
|
|
936
|
+
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
937
|
+
if (raw === "0 0 #0000" || raw === "none") continue;
|
|
938
|
+
const value = SINGLE_VAR.test(raw) ? resolveValue(raw, props) : raw;
|
|
939
|
+
if (value === null) continue;
|
|
940
|
+
seen.add(unwrapShadowColor(value).toLowerCase());
|
|
881
941
|
}
|
|
882
942
|
return [...seen];
|
|
883
943
|
}
|
|
@@ -892,15 +952,18 @@ function durationsMs(css) {
|
|
|
892
952
|
}
|
|
893
953
|
function checkRestraint(input) {
|
|
894
954
|
const budget = { ...HOUSE_BUDGET, ...input.budget };
|
|
955
|
+
const whole = stripComments(input.css);
|
|
956
|
+
const props = customProperties(whole);
|
|
895
957
|
const css = stripLayers(
|
|
896
|
-
|
|
958
|
+
whole,
|
|
897
959
|
input.ignoreAtLayers ?? DEFAULT_IGNORED_LAYERS
|
|
898
960
|
);
|
|
961
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
899
962
|
const violations = [];
|
|
900
963
|
const push = (rule, severity, message, found) => {
|
|
901
964
|
violations.push({ rule, severity, message, found });
|
|
902
965
|
};
|
|
903
|
-
const fontSizes = distinctValues(css, "font-size");
|
|
966
|
+
const fontSizes = distinctValues(css, "font-size", props, unresolved);
|
|
904
967
|
if (fontSizes.length > budget.maxFontSizes) {
|
|
905
968
|
push(
|
|
906
969
|
"font-sizes",
|
|
@@ -909,10 +972,15 @@ function checkRestraint(input) {
|
|
|
909
972
|
fontSizes
|
|
910
973
|
);
|
|
911
974
|
}
|
|
912
|
-
const weights = distinctValues(css, "font-weight").filter(
|
|
913
|
-
|
|
914
|
-
(w) => w === "normal" || w === "bold"
|
|
975
|
+
const weights = distinctValues(css, "font-weight", props, unresolved).filter(
|
|
976
|
+
(w) => w !== "normal" && w !== "bold"
|
|
915
977
|
);
|
|
978
|
+
const namedWeights = distinctValues(
|
|
979
|
+
css,
|
|
980
|
+
"font-weight",
|
|
981
|
+
props,
|
|
982
|
+
unresolved
|
|
983
|
+
).filter((w) => w === "normal" || w === "bold");
|
|
916
984
|
const allWeights = [.../* @__PURE__ */ new Set([...weights, ...namedWeights])];
|
|
917
985
|
if (allWeights.length > budget.maxFontWeights) {
|
|
918
986
|
push(
|
|
@@ -922,7 +990,7 @@ function checkRestraint(input) {
|
|
|
922
990
|
allWeights
|
|
923
991
|
);
|
|
924
992
|
}
|
|
925
|
-
const families = distinctValues(css, "font-family").filter(
|
|
993
|
+
const families = distinctValues(css, "font-family", props, unresolved).filter(
|
|
926
994
|
(f) => !/\bmonospace\b|\bmono\b/.test(f)
|
|
927
995
|
);
|
|
928
996
|
if (families.length > budget.maxFontFamilies) {
|
|
@@ -933,11 +1001,23 @@ function checkRestraint(input) {
|
|
|
933
1001
|
families
|
|
934
1002
|
);
|
|
935
1003
|
}
|
|
936
|
-
const radii = distinctValues(css, "border-radius").filter(
|
|
1004
|
+
const radii = distinctValues(css, "border-radius", props, unresolved).filter(
|
|
1005
|
+
(r) => r !== "0" && r !== "0px"
|
|
1006
|
+
);
|
|
937
1007
|
if (radii.length > budget.maxRadii) {
|
|
938
|
-
push(
|
|
1008
|
+
push(
|
|
1009
|
+
"radii",
|
|
1010
|
+
"error",
|
|
1011
|
+
`${radii.length} corner radii, budget ${budget.maxRadii}.`,
|
|
1012
|
+
radii
|
|
1013
|
+
);
|
|
939
1014
|
}
|
|
940
|
-
const shadows =
|
|
1015
|
+
const shadows = [
|
|
1016
|
+
.../* @__PURE__ */ new Set([
|
|
1017
|
+
...distinctValues(css, "box-shadow", props, unresolved),
|
|
1018
|
+
...twShadowValues(css, props)
|
|
1019
|
+
])
|
|
1020
|
+
].filter((s) => s !== "none");
|
|
941
1021
|
if (shadows.length > budget.maxShadows) {
|
|
942
1022
|
push(
|
|
943
1023
|
"shadows",
|
|
@@ -946,6 +1026,14 @@ function checkRestraint(input) {
|
|
|
946
1026
|
shadows
|
|
947
1027
|
);
|
|
948
1028
|
}
|
|
1029
|
+
if (unresolved.size > 0) {
|
|
1030
|
+
push(
|
|
1031
|
+
"unresolved-token",
|
|
1032
|
+
"warning",
|
|
1033
|
+
`${unresolved.size} declaration(s) reference a custom property that is never defined and carry no fallback. Each one applies nothing.`,
|
|
1034
|
+
[...unresolved].sort()
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
949
1037
|
const accentHexes = [
|
|
950
1038
|
...css.matchAll(/--[a-z0-9-]*accent[a-z0-9-]*\s*:\s*(#[0-9a-fA-F]{3,8})/g)
|
|
951
1039
|
].map((m) => m[1]);
|
|
@@ -997,7 +1085,9 @@ function checkRestraint(input) {
|
|
|
997
1085
|
[...new Set(easeIn)]
|
|
998
1086
|
);
|
|
999
1087
|
}
|
|
1000
|
-
const overBudget = durationsMs(css).filter(
|
|
1088
|
+
const overBudget = durationsMs(css).filter(
|
|
1089
|
+
(d) => d.ms > budget.maxUiDurationMs
|
|
1090
|
+
);
|
|
1001
1091
|
if (overBudget.length > 0) {
|
|
1002
1092
|
push(
|
|
1003
1093
|
"duration",
|
|
@@ -1014,7 +1104,10 @@ function checkRestraint(input) {
|
|
|
1014
1104
|
["scale(0)"]
|
|
1015
1105
|
);
|
|
1016
1106
|
}
|
|
1017
|
-
const reducedMotionBlocks = atRuleBodies(
|
|
1107
|
+
const reducedMotionBlocks = atRuleBodies(
|
|
1108
|
+
css,
|
|
1109
|
+
/@media[^{]*prefers-reduced-motion:\s*reduce[^{]*\{/gi
|
|
1110
|
+
);
|
|
1018
1111
|
if (reducedMotionBlocks.length === 0) {
|
|
1019
1112
|
if (/@keyframes|animation\s*:|transition\s*:/.test(css)) {
|
|
1020
1113
|
push(
|
package/dist/index.js
CHANGED
|
@@ -780,10 +780,19 @@ function stripLayers(css, ignored) {
|
|
|
780
780
|
}
|
|
781
781
|
return out;
|
|
782
782
|
}
|
|
783
|
-
var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
783
|
+
var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
784
|
+
"inherit",
|
|
785
|
+
"initial",
|
|
786
|
+
"unset",
|
|
787
|
+
"revert",
|
|
788
|
+
"revert-layer"
|
|
789
|
+
]);
|
|
784
790
|
function atRuleBodies(css, prelude) {
|
|
785
791
|
const out = [];
|
|
786
|
-
const pattern = new RegExp(
|
|
792
|
+
const pattern = new RegExp(
|
|
793
|
+
prelude.source,
|
|
794
|
+
prelude.flags.includes("g") ? prelude.flags : `${prelude.flags}g`
|
|
795
|
+
);
|
|
787
796
|
let match;
|
|
788
797
|
while ((match = pattern.exec(css)) !== null) {
|
|
789
798
|
const start = match.index + match[0].length;
|
|
@@ -799,13 +808,64 @@ function atRuleBodies(css, prelude) {
|
|
|
799
808
|
}
|
|
800
809
|
return out;
|
|
801
810
|
}
|
|
802
|
-
function
|
|
811
|
+
function customProperties(css) {
|
|
812
|
+
const out = /* @__PURE__ */ new Map();
|
|
813
|
+
for (const match of css.matchAll(/(--[\w-]+)\s*:\s*([^;}]+)/g)) {
|
|
814
|
+
out.set(match[1].toLowerCase(), match[2].trim().replace(/\s+/g, " "));
|
|
815
|
+
}
|
|
816
|
+
return out;
|
|
817
|
+
}
|
|
818
|
+
function unwrapShadowColor(value) {
|
|
819
|
+
return value.replace(
|
|
820
|
+
/var\(\s*--tw-shadow-color\s*,\s*([^)]*(?:\([^)]*\))?[^)]*)\)/g,
|
|
821
|
+
"$1"
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
var SINGLE_VAR = /^var\(\s*(--[\w-]+)\s*(?:,([\s\S]*))?\)$/;
|
|
825
|
+
function resolveValue(value, props, depth = 0) {
|
|
826
|
+
if (depth > 8) return null;
|
|
827
|
+
const match = SINGLE_VAR.exec(value.trim());
|
|
828
|
+
if (match === null) return value.trim();
|
|
829
|
+
const target = props.get(match[1].toLowerCase());
|
|
830
|
+
if (target !== void 0 && target !== "initial")
|
|
831
|
+
return resolveValue(target, props, depth + 1);
|
|
832
|
+
const fallback = match[2]?.trim();
|
|
833
|
+
return fallback === void 0 || fallback === "" ? null : resolveValue(fallback, props, depth + 1);
|
|
834
|
+
}
|
|
835
|
+
function isVarOnlyList(value) {
|
|
836
|
+
const parts = value.split(",");
|
|
837
|
+
return parts.length > 1 && parts.every((part) => /^\s*var\(\s*--[\w-]+\s*\)\s*$/.test(part));
|
|
838
|
+
}
|
|
839
|
+
function distinctValues(css, property, props, unresolved) {
|
|
803
840
|
const pattern = new RegExp(`(?:^|[;{\\s])${property}\\s*:\\s*([^;}]+)`, "gi");
|
|
804
841
|
const seen = /* @__PURE__ */ new Set();
|
|
805
842
|
for (const match of css.matchAll(pattern)) {
|
|
806
|
-
const
|
|
807
|
-
if (
|
|
808
|
-
|
|
843
|
+
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
844
|
+
if (CSS_WIDE_KEYWORDS.has(raw)) continue;
|
|
845
|
+
const value = unwrapShadowColor(raw);
|
|
846
|
+
if (isVarOnlyList(value)) continue;
|
|
847
|
+
if (!SINGLE_VAR.test(value)) {
|
|
848
|
+
seen.add(value);
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
if (props === void 0) continue;
|
|
852
|
+
const resolved = resolveValue(value, props);
|
|
853
|
+
if (resolved === null) {
|
|
854
|
+
unresolved?.add(`${property}: ${value}`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
seen.add(unwrapShadowColor(resolved).toLowerCase());
|
|
858
|
+
}
|
|
859
|
+
return [...seen];
|
|
860
|
+
}
|
|
861
|
+
function twShadowValues(css, props) {
|
|
862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
863
|
+
for (const match of css.matchAll(/--tw-shadow\s*:\s*([^;}]+)/g)) {
|
|
864
|
+
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
865
|
+
if (raw === "0 0 #0000" || raw === "none") continue;
|
|
866
|
+
const value = SINGLE_VAR.test(raw) ? resolveValue(raw, props) : raw;
|
|
867
|
+
if (value === null) continue;
|
|
868
|
+
seen.add(unwrapShadowColor(value).toLowerCase());
|
|
809
869
|
}
|
|
810
870
|
return [...seen];
|
|
811
871
|
}
|
|
@@ -820,15 +880,18 @@ function durationsMs(css) {
|
|
|
820
880
|
}
|
|
821
881
|
function checkRestraint(input) {
|
|
822
882
|
const budget = { ...HOUSE_BUDGET, ...input.budget };
|
|
883
|
+
const whole = stripComments(input.css);
|
|
884
|
+
const props = customProperties(whole);
|
|
823
885
|
const css = stripLayers(
|
|
824
|
-
|
|
886
|
+
whole,
|
|
825
887
|
input.ignoreAtLayers ?? DEFAULT_IGNORED_LAYERS
|
|
826
888
|
);
|
|
889
|
+
const unresolved = /* @__PURE__ */ new Set();
|
|
827
890
|
const violations = [];
|
|
828
891
|
const push = (rule, severity, message, found) => {
|
|
829
892
|
violations.push({ rule, severity, message, found });
|
|
830
893
|
};
|
|
831
|
-
const fontSizes = distinctValues(css, "font-size");
|
|
894
|
+
const fontSizes = distinctValues(css, "font-size", props, unresolved);
|
|
832
895
|
if (fontSizes.length > budget.maxFontSizes) {
|
|
833
896
|
push(
|
|
834
897
|
"font-sizes",
|
|
@@ -837,10 +900,15 @@ function checkRestraint(input) {
|
|
|
837
900
|
fontSizes
|
|
838
901
|
);
|
|
839
902
|
}
|
|
840
|
-
const weights = distinctValues(css, "font-weight").filter(
|
|
841
|
-
|
|
842
|
-
(w) => w === "normal" || w === "bold"
|
|
903
|
+
const weights = distinctValues(css, "font-weight", props, unresolved).filter(
|
|
904
|
+
(w) => w !== "normal" && w !== "bold"
|
|
843
905
|
);
|
|
906
|
+
const namedWeights = distinctValues(
|
|
907
|
+
css,
|
|
908
|
+
"font-weight",
|
|
909
|
+
props,
|
|
910
|
+
unresolved
|
|
911
|
+
).filter((w) => w === "normal" || w === "bold");
|
|
844
912
|
const allWeights = [.../* @__PURE__ */ new Set([...weights, ...namedWeights])];
|
|
845
913
|
if (allWeights.length > budget.maxFontWeights) {
|
|
846
914
|
push(
|
|
@@ -850,7 +918,7 @@ function checkRestraint(input) {
|
|
|
850
918
|
allWeights
|
|
851
919
|
);
|
|
852
920
|
}
|
|
853
|
-
const families = distinctValues(css, "font-family").filter(
|
|
921
|
+
const families = distinctValues(css, "font-family", props, unresolved).filter(
|
|
854
922
|
(f) => !/\bmonospace\b|\bmono\b/.test(f)
|
|
855
923
|
);
|
|
856
924
|
if (families.length > budget.maxFontFamilies) {
|
|
@@ -861,11 +929,23 @@ function checkRestraint(input) {
|
|
|
861
929
|
families
|
|
862
930
|
);
|
|
863
931
|
}
|
|
864
|
-
const radii = distinctValues(css, "border-radius").filter(
|
|
932
|
+
const radii = distinctValues(css, "border-radius", props, unresolved).filter(
|
|
933
|
+
(r) => r !== "0" && r !== "0px"
|
|
934
|
+
);
|
|
865
935
|
if (radii.length > budget.maxRadii) {
|
|
866
|
-
push(
|
|
936
|
+
push(
|
|
937
|
+
"radii",
|
|
938
|
+
"error",
|
|
939
|
+
`${radii.length} corner radii, budget ${budget.maxRadii}.`,
|
|
940
|
+
radii
|
|
941
|
+
);
|
|
867
942
|
}
|
|
868
|
-
const shadows =
|
|
943
|
+
const shadows = [
|
|
944
|
+
.../* @__PURE__ */ new Set([
|
|
945
|
+
...distinctValues(css, "box-shadow", props, unresolved),
|
|
946
|
+
...twShadowValues(css, props)
|
|
947
|
+
])
|
|
948
|
+
].filter((s) => s !== "none");
|
|
869
949
|
if (shadows.length > budget.maxShadows) {
|
|
870
950
|
push(
|
|
871
951
|
"shadows",
|
|
@@ -874,6 +954,14 @@ function checkRestraint(input) {
|
|
|
874
954
|
shadows
|
|
875
955
|
);
|
|
876
956
|
}
|
|
957
|
+
if (unresolved.size > 0) {
|
|
958
|
+
push(
|
|
959
|
+
"unresolved-token",
|
|
960
|
+
"warning",
|
|
961
|
+
`${unresolved.size} declaration(s) reference a custom property that is never defined and carry no fallback. Each one applies nothing.`,
|
|
962
|
+
[...unresolved].sort()
|
|
963
|
+
);
|
|
964
|
+
}
|
|
877
965
|
const accentHexes = [
|
|
878
966
|
...css.matchAll(/--[a-z0-9-]*accent[a-z0-9-]*\s*:\s*(#[0-9a-fA-F]{3,8})/g)
|
|
879
967
|
].map((m) => m[1]);
|
|
@@ -925,7 +1013,9 @@ function checkRestraint(input) {
|
|
|
925
1013
|
[...new Set(easeIn)]
|
|
926
1014
|
);
|
|
927
1015
|
}
|
|
928
|
-
const overBudget = durationsMs(css).filter(
|
|
1016
|
+
const overBudget = durationsMs(css).filter(
|
|
1017
|
+
(d) => d.ms > budget.maxUiDurationMs
|
|
1018
|
+
);
|
|
929
1019
|
if (overBudget.length > 0) {
|
|
930
1020
|
push(
|
|
931
1021
|
"duration",
|
|
@@ -942,7 +1032,10 @@ function checkRestraint(input) {
|
|
|
942
1032
|
["scale(0)"]
|
|
943
1033
|
);
|
|
944
1034
|
}
|
|
945
|
-
const reducedMotionBlocks = atRuleBodies(
|
|
1035
|
+
const reducedMotionBlocks = atRuleBodies(
|
|
1036
|
+
css,
|
|
1037
|
+
/@media[^{]*prefers-reduced-motion:\s*reduce[^{]*\{/gi
|
|
1038
|
+
);
|
|
946
1039
|
if (reducedMotionBlocks.length === 0) {
|
|
947
1040
|
if (/@keyframes|animation\s*:|transition\s*:/.test(css)) {
|
|
948
1041
|
push(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domandigital/craft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|