@tenphi/tasty 3.0.1 → 3.1.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.
Files changed (50) hide show
  1. package/README.md +16 -16
  2. package/dist/{babel-HlEpSZrj.d.ts → babel-R2qT7yLN.d.ts} +2 -2
  3. package/dist/{collector-D14DcZx9.js → collector-D7yzmG5G.js} +3 -3
  4. package/dist/{collector-D14DcZx9.js.map → collector-D7yzmG5G.js.map} +1 -1
  5. package/dist/{collector-BPzZezAG.d.ts → collector-D8o4Wdq-.d.ts} +2 -2
  6. package/dist/{config-CCcE_tqx.js → config-CwQ-fAsp.js} +674 -330
  7. package/dist/config-CwQ-fAsp.js.map +1 -0
  8. package/dist/{config-D2INrjr_.d.ts → config-De8L9NWM.d.ts} +2 -2
  9. package/dist/core/index.d.ts +4 -4
  10. package/dist/core/index.js +5 -5
  11. package/dist/{core-B5d0IPi2.js → core-CqSh853z.js} +11 -12
  12. package/dist/core-CqSh853z.js.map +1 -0
  13. package/dist/{css-writer-B5DBoxhf.js → css-writer-dS1srTkS.js} +3 -3
  14. package/dist/{css-writer-B5DBoxhf.js.map → css-writer-dS1srTkS.js.map} +1 -1
  15. package/dist/{format-rules-BbKDjsfO.js → format-rules-CaW4lJGg.js} +11 -9
  16. package/dist/format-rules-CaW4lJGg.js.map +1 -0
  17. package/dist/{hydrate-CwALW3J9.js → hydrate-uFv9kx7G.js} +2 -2
  18. package/dist/{hydrate-CwALW3J9.js.map → hydrate-uFv9kx7G.js.map} +1 -1
  19. package/dist/{index-BAAbdfRs.d.ts → index-DzGxoeyN.d.ts} +5 -5
  20. package/dist/{index-l_NdJSUS.d.ts → index-KUYya3x7.d.ts} +11 -1
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.js +6 -6
  23. package/dist/{keyframes-B8M1FmtE.js → keyframes-DaZhjqkd.js} +2 -2
  24. package/dist/{keyframes-B8M1FmtE.js.map → keyframes-DaZhjqkd.js.map} +1 -1
  25. package/dist/{merge-styles-NEvqayL_.d.ts → merge-styles-DLm4wdnb.d.ts} +2 -2
  26. package/dist/{merge-styles--1zOSFlS.js → merge-styles-H8LFJ5HY.js} +2 -2
  27. package/dist/{merge-styles--1zOSFlS.js.map → merge-styles-H8LFJ5HY.js.map} +1 -1
  28. package/dist/{resolve-recipes-Caix5NnH.js → resolve-recipes-aN94fjqS.js} +3 -3
  29. package/dist/{resolve-recipes-Caix5NnH.js.map → resolve-recipes-aN94fjqS.js.map} +1 -1
  30. package/dist/ssr/astro-client.js +1 -1
  31. package/dist/ssr/astro.js +3 -3
  32. package/dist/ssr/index.d.ts +1 -1
  33. package/dist/ssr/index.js +3 -3
  34. package/dist/ssr/next.d.ts +1 -1
  35. package/dist/ssr/next.js +4 -4
  36. package/dist/static/index.d.ts +2 -2
  37. package/dist/static/index.js +1 -1
  38. package/dist/zero/babel.d.ts +1 -1
  39. package/dist/zero/babel.js +4 -4
  40. package/dist/zero/index.d.ts +1 -1
  41. package/dist/zero/index.js +1 -1
  42. package/dist/zero/next.d.ts +1 -1
  43. package/docs/ai-agents.md +3 -0
  44. package/docs/configuration.md +11 -6
  45. package/docs/dsl.md +99 -2
  46. package/docs/styles.md +3 -1
  47. package/package.json +14 -10
  48. package/dist/config-CCcE_tqx.js.map +0 -1
  49. package/dist/core-B5d0IPi2.js.map +0 -1
  50. package/dist/format-rules-BbKDjsfO.js.map +0 -1
@@ -1,136 +1,141 @@
1
- //#region src/parser/lru.ts
2
- var Lru = class {
3
- limit;
4
- map = /* @__PURE__ */ new Map();
5
- head = null;
6
- tail = null;
7
- onEvict;
8
- constructor(limit = 1e3, onEvict) {
9
- this.limit = limit;
10
- let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
11
- if (normalized <= 0) normalized = 1e3;
12
- this.limit = normalized;
13
- this.onEvict = onEvict;
14
- }
15
- setOnEvict(fn) {
16
- this.onEvict = fn;
17
- }
18
- get(key) {
19
- const node = this.map.get(key);
20
- if (!node) return void 0;
21
- this.touch(key, node);
22
- return node.value;
23
- }
24
- set(key, value) {
25
- let node = this.map.get(key);
26
- if (node) {
27
- node.value = value;
28
- this.touch(key, node);
29
- return;
30
- }
31
- node = {
32
- prev: null,
33
- next: this.head,
34
- value
35
- };
36
- if (this.head) {
37
- const headNode = this.map.get(this.head);
38
- if (headNode) headNode.prev = key;
39
- }
40
- this.head = key;
41
- if (!this.tail) this.tail = key;
42
- this.map.set(key, node);
43
- if (this.map.size > this.limit) this.evict();
44
- }
45
- delete(key) {
46
- const node = this.map.get(key);
47
- if (!node) return;
48
- if (node.prev) {
49
- const prevNode = this.map.get(node.prev);
50
- if (prevNode) prevNode.next = node.next;
51
- }
52
- if (node.next) {
53
- const nextNode = this.map.get(node.next);
54
- if (nextNode) nextNode.prev = node.prev;
55
- }
56
- if (this.head === key) this.head = node.next;
57
- if (this.tail === key) this.tail = node.prev;
58
- this.map.delete(key);
59
- }
60
- keys() {
61
- return this.map.keys();
62
- }
63
- touch(key, node) {
64
- if (this.head === key) return;
65
- if (node.prev) {
66
- const prevNode = this.map.get(node.prev);
67
- if (prevNode) prevNode.next = node.next;
68
- }
69
- if (node.next) {
70
- const nextNode = this.map.get(node.next);
71
- if (nextNode) nextNode.prev = node.prev;
72
- }
73
- if (this.tail === key) this.tail = node.prev;
74
- node.prev = null;
75
- node.next = this.head;
76
- if (this.head) {
77
- const headNode = this.map.get(this.head);
78
- if (headNode) headNode.prev = key;
79
- }
80
- this.head = key;
81
- }
82
- evict() {
83
- const old = this.tail;
84
- if (!old) return;
85
- const node = this.map.get(old);
86
- if (!node) {
87
- if (this.head === old) this.head = null;
88
- this.tail = null;
89
- return;
90
- }
91
- if (node.prev) {
92
- const prevNode = this.map.get(node.prev);
93
- if (prevNode) prevNode.next = null;
94
- }
95
- this.tail = node.prev;
96
- if (this.head === old) this.head = null;
97
- this.map.delete(old);
98
- if (this.onEvict) try {
99
- this.onEvict(old, node.value);
100
- } catch {}
101
- }
102
- clear() {
103
- this.map.clear();
104
- this.head = this.tail = null;
105
- }
106
- };
1
+ //#region src/parser/const.ts
2
+ const VALUE_KEYWORDS = new Set([
3
+ "auto",
4
+ "max-content",
5
+ "min-content",
6
+ "fit-content",
7
+ "stretch",
8
+ "initial",
9
+ "inherit",
10
+ "revert",
11
+ "unset",
12
+ "revert-layer"
13
+ ]);
14
+ const CSS_WIDE_KEYWORDS = new Set([
15
+ "initial",
16
+ "inherit",
17
+ "revert",
18
+ "unset",
19
+ "revert-layer"
20
+ ]);
21
+ /**
22
+ * Color functions that *derive* a color from other colors. They take no alpha
23
+ * channel, so opacity has to wrap the whole call in `color-mix()` instead of
24
+ * being appended after a slash.
25
+ */
26
+ const DERIVED_COLOR_FUNCS_LIST = [
27
+ "color-mix",
28
+ "color-contrast",
29
+ "contrast-color",
30
+ "light-dark"
31
+ ];
32
+ const DERIVED_COLOR_FUNCS = new Set(DERIVED_COLOR_FUNCS_LIST);
33
+ /**
34
+ * Every color function the parser recognizes. The ones outside
35
+ * `DERIVED_COLOR_FUNCS` take channels as arguments, optionally followed by
36
+ * `/ <alpha>` — which is where an opacity suffix writes.
37
+ */
38
+ const COLOR_FUNCS = new Set([
39
+ "rgb",
40
+ "rgba",
41
+ "hsl",
42
+ "hsla",
43
+ "hwb",
44
+ "lab",
45
+ "lch",
46
+ "oklab",
47
+ "oklch",
48
+ "color",
49
+ "device-cmyk",
50
+ "gray",
51
+ ...DERIVED_COLOR_FUNCS_LIST
52
+ ]);
53
+ /** A value that is a single `name(args)` call: captures the name and the args. */
54
+ const RE_FUNC_CALL = /^([a-z][a-z0-9-]*)\((.+)\)$/i;
55
+ /**
56
+ * The color function a value calls at its top level, lowercased — or `null` when
57
+ * the value is not a single color function call.
58
+ */
59
+ function colorFuncName(value) {
60
+ const match = value.match(RE_FUNC_CALL);
61
+ if (!match) return null;
62
+ const name = match[1].toLowerCase();
63
+ return COLOR_FUNCS.has(name) ? name : null;
64
+ }
65
+ const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
66
+ const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
67
+ const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
68
+ const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
69
+ const CANONICAL_FUNC_CASE = new Map([
70
+ ["translatex", "translateX"],
71
+ ["translatey", "translateY"],
72
+ ["translatez", "translateZ"],
73
+ ["scalex", "scaleX"],
74
+ ["scaley", "scaleY"],
75
+ ["scalez", "scaleZ"],
76
+ ["rotatex", "rotateX"],
77
+ ["rotatey", "rotateY"],
78
+ ["rotatez", "rotateZ"],
79
+ ["skewx", "skewX"],
80
+ ["skewy", "skewY"]
81
+ ]);
82
+ function canonicalFuncName(lowered) {
83
+ return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
84
+ }
107
85
  //#endregion
108
- //#region src/utils/function-color.ts
109
- const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
110
- const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
86
+ //#region src/utils/string.ts
87
+ function toSnakeCase(str) {
88
+ return str.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`);
89
+ }
111
90
  /**
112
- * Resolve a `name(...)` value produced by a registered custom parse function
113
- * into its concrete color output.
91
+ * Normalize a DSL identifier to the CSS custom-property name it emits.
114
92
  *
115
- * A color function is just a `functions` entry whose output is an already
116
- * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
117
- * value to the global parser (which already runs the registered parse function)
118
- * and returns the result only when it looks like a color. Returns `null` when
119
- * `str` is not a registered custom function or its output is not a color.
93
+ * CSS custom-property names are case-sensitive, so `$myVar` has to keep its
94
+ * inner case to reference `--myVar`. A leading capital is the one exception:
95
+ * `$Foo` is not a supported way to name a property, so the first character is
96
+ * folded rather than kebab-cased (`--foo`, not `---foo`) and every later
97
+ * character is left alone.
120
98
  *
121
- * This is the generic replacement for the previously hardcoded okhsl/okhst
122
- * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
123
- * the `#token.alpha` injection path.
99
+ * Every place that turns a `$name` / `#name` identifier into a CSS name goes
100
+ * through this, so authoring a token and referencing it always agree.
124
101
  */
125
- function resolveFunctionColor(str) {
126
- const m = RE_FUNC_NAME.exec(str);
127
- if (!m) return null;
128
- const name = m[1].toLowerCase();
129
- getGlobalParser();
130
- if (!(name in getGlobalParseFunctions())) return null;
131
- const out = getGlobalParser().process(str).output;
132
- if (!out || !RE_COLOR_OUT.test(out)) return null;
133
- return out;
102
+ function normalizeDslName(name) {
103
+ if (!name) return name;
104
+ const first = name[0];
105
+ const lower = first.toLowerCase();
106
+ return lower === first ? name : lower + name.slice(1);
107
+ }
108
+ /** A DSL identifier: `$name`, `$$name`, `#name` or `##name`. */
109
+ const RE_DSL_IDENTIFIER = /(\$\$?|##?)([a-zA-Z_][a-zA-Z0-9_-]*)/g;
110
+ /**
111
+ * Case-fold a DSL string for parsing, preserving the case of custom-property
112
+ * names.
113
+ *
114
+ * Everything the parser matches on — keywords, units, modifiers, function names,
115
+ * color-token names — is compared lowercase, so the whole string used to be
116
+ * lowercased up front. That also folded custom-property names, which are
117
+ * case-sensitive in CSS: `$myVar` referenced `var(--myvar)` while the token
118
+ * definition emitted `--myVar`, so the two could never meet.
119
+ *
120
+ * Identifier bodies now keep their case (their first character still folds, see
121
+ * {@link normalizeDslName}); everything else is lowercased exactly as before.
122
+ * Hex literals are excluded — `#FF0000` is a color, not a name, and must fold to
123
+ * match {@link RE_HEX}.
124
+ */
125
+ function foldDslCase(src) {
126
+ const folded = src.toLowerCase();
127
+ if (folded === src || !src.includes("$") && !src.includes("#")) return folded;
128
+ RE_DSL_IDENTIFIER.lastIndex = 0;
129
+ let out = "";
130
+ let last = 0;
131
+ let match;
132
+ while (match = RE_DSL_IDENTIFIER.exec(src)) {
133
+ const [whole, prefix, body] = match;
134
+ out += src.slice(last, match.index).toLowerCase();
135
+ out += prefix + (prefix[0] === "#" && RE_HEX.test(body.toLowerCase()) ? body.toLowerCase() : normalizeDslName(body));
136
+ last = match.index + whole.length;
137
+ }
138
+ return out + src.slice(last).toLowerCase();
134
139
  }
135
140
  //#endregion
136
141
  //#region src/utils/color-math.ts
@@ -844,32 +849,167 @@ function hslStringToRgb(hslStr) {
844
849
  return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
845
850
  }
846
851
  /**
847
- * Convert an `oklch()` color string to an `rgb()`/`rgba()` CSS string.
848
- * Supports deg/turn/rad hue units and percentage lightness.
852
+ * Convert an `oklch()` color string to an `rgb()`/`rgba()` CSS string.
853
+ * Supports deg/turn/rad hue units and percentage lightness.
854
+ */
855
+ function oklchStringToRgb(oklchStr) {
856
+ const match = oklchStr.match(/oklch\(([^)]+)\)/i);
857
+ if (!match) return null;
858
+ const [colorPart, alphaPart] = match[1].trim().split("/");
859
+ const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
860
+ if (parts.length < 3) return null;
861
+ const parsePercent = (val) => {
862
+ const num = parseFloat(val);
863
+ return val.includes("%") ? num / 100 : num;
864
+ };
865
+ const L = Math.max(0, Math.min(1, parsePercent(parts[0])));
866
+ const C = Math.max(0, parseFloat(parts[1]));
867
+ let H = parseFloat(parts[2]);
868
+ const hueStr = parts[2].toLowerCase();
869
+ if (hueStr.endsWith("turn")) H = parseFloat(hueStr) * 360;
870
+ else if (hueStr.endsWith("rad")) H = parseFloat(hueStr) * 180 / Math.PI;
871
+ else if (hueStr.endsWith("deg")) H = parseFloat(hueStr);
872
+ const [r, g, b] = oklchToRgbValues(L, C, H);
873
+ if (alphaPart) {
874
+ const alpha = parseFloat(alphaPart.trim());
875
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;
876
+ }
877
+ return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
878
+ }
879
+ //#endregion
880
+ //#region src/parser/lru.ts
881
+ var Lru = class {
882
+ limit;
883
+ map = /* @__PURE__ */ new Map();
884
+ head = null;
885
+ tail = null;
886
+ onEvict;
887
+ constructor(limit = 1e3, onEvict) {
888
+ this.limit = limit;
889
+ let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
890
+ if (normalized <= 0) normalized = 1e3;
891
+ this.limit = normalized;
892
+ this.onEvict = onEvict;
893
+ }
894
+ setOnEvict(fn) {
895
+ this.onEvict = fn;
896
+ }
897
+ get(key) {
898
+ const node = this.map.get(key);
899
+ if (!node) return void 0;
900
+ this.touch(key, node);
901
+ return node.value;
902
+ }
903
+ set(key, value) {
904
+ let node = this.map.get(key);
905
+ if (node) {
906
+ node.value = value;
907
+ this.touch(key, node);
908
+ return;
909
+ }
910
+ node = {
911
+ prev: null,
912
+ next: this.head,
913
+ value
914
+ };
915
+ if (this.head) {
916
+ const headNode = this.map.get(this.head);
917
+ if (headNode) headNode.prev = key;
918
+ }
919
+ this.head = key;
920
+ if (!this.tail) this.tail = key;
921
+ this.map.set(key, node);
922
+ if (this.map.size > this.limit) this.evict();
923
+ }
924
+ delete(key) {
925
+ const node = this.map.get(key);
926
+ if (!node) return;
927
+ if (node.prev) {
928
+ const prevNode = this.map.get(node.prev);
929
+ if (prevNode) prevNode.next = node.next;
930
+ }
931
+ if (node.next) {
932
+ const nextNode = this.map.get(node.next);
933
+ if (nextNode) nextNode.prev = node.prev;
934
+ }
935
+ if (this.head === key) this.head = node.next;
936
+ if (this.tail === key) this.tail = node.prev;
937
+ this.map.delete(key);
938
+ }
939
+ keys() {
940
+ return this.map.keys();
941
+ }
942
+ touch(key, node) {
943
+ if (this.head === key) return;
944
+ if (node.prev) {
945
+ const prevNode = this.map.get(node.prev);
946
+ if (prevNode) prevNode.next = node.next;
947
+ }
948
+ if (node.next) {
949
+ const nextNode = this.map.get(node.next);
950
+ if (nextNode) nextNode.prev = node.prev;
951
+ }
952
+ if (this.tail === key) this.tail = node.prev;
953
+ node.prev = null;
954
+ node.next = this.head;
955
+ if (this.head) {
956
+ const headNode = this.map.get(this.head);
957
+ if (headNode) headNode.prev = key;
958
+ }
959
+ this.head = key;
960
+ }
961
+ evict() {
962
+ const old = this.tail;
963
+ if (!old) return;
964
+ const node = this.map.get(old);
965
+ if (!node) {
966
+ if (this.head === old) this.head = null;
967
+ this.tail = null;
968
+ return;
969
+ }
970
+ if (node.prev) {
971
+ const prevNode = this.map.get(node.prev);
972
+ if (prevNode) prevNode.next = null;
973
+ }
974
+ this.tail = node.prev;
975
+ if (this.head === old) this.head = null;
976
+ this.map.delete(old);
977
+ if (this.onEvict) try {
978
+ this.onEvict(old, node.value);
979
+ } catch {}
980
+ }
981
+ clear() {
982
+ this.map.clear();
983
+ this.head = this.tail = null;
984
+ }
985
+ };
986
+ //#endregion
987
+ //#region src/utils/function-color.ts
988
+ const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
989
+ const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
990
+ /**
991
+ * Resolve a `name(...)` value produced by a registered custom parse function
992
+ * into its concrete color output.
993
+ *
994
+ * A color function is just a `functions` entry whose output is an already
995
+ * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
996
+ * value to the global parser (which already runs the registered parse function)
997
+ * and returns the result only when it looks like a color. Returns `null` when
998
+ * `str` is not a registered custom function or its output is not a color.
999
+ *
1000
+ * This is the generic replacement for the previously hardcoded okhsl/okhst
1001
+ * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
1002
+ * the `#token.alpha` injection path.
849
1003
  */
850
- function oklchStringToRgb(oklchStr) {
851
- const match = oklchStr.match(/oklch\(([^)]+)\)/i);
852
- if (!match) return null;
853
- const [colorPart, alphaPart] = match[1].trim().split("/");
854
- const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
855
- if (parts.length < 3) return null;
856
- const parsePercent = (val) => {
857
- const num = parseFloat(val);
858
- return val.includes("%") ? num / 100 : num;
859
- };
860
- const L = Math.max(0, Math.min(1, parsePercent(parts[0])));
861
- const C = Math.max(0, parseFloat(parts[1]));
862
- let H = parseFloat(parts[2]);
863
- const hueStr = parts[2].toLowerCase();
864
- if (hueStr.endsWith("turn")) H = parseFloat(hueStr) * 360;
865
- else if (hueStr.endsWith("rad")) H = parseFloat(hueStr) * 180 / Math.PI;
866
- else if (hueStr.endsWith("deg")) H = parseFloat(hueStr);
867
- const [r, g, b] = oklchToRgbValues(L, C, H);
868
- if (alphaPart) {
869
- const alpha = parseFloat(alphaPart.trim());
870
- return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;
871
- }
872
- return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
1004
+ function resolveFunctionColor(str) {
1005
+ const m = RE_FUNC_NAME.exec(str);
1006
+ if (!m) return null;
1007
+ const name = m[1].toLowerCase();
1008
+ getGlobalParser();
1009
+ if (!(name in getGlobalParseFunctions())) return null;
1010
+ const out = getGlobalParser().process(str).output;
1011
+ if (!out || !RE_COLOR_OUT.test(out)) return null;
1012
+ return out;
873
1013
  }
874
1014
  //#endregion
875
1015
  //#region src/utils/color-space.ts
@@ -891,9 +1031,6 @@ function resetColorSpace() {
891
1031
  function getColorSpaceSuffix() {
892
1032
  return currentColorSpace;
893
1033
  }
894
- function getColorSpaceFunc() {
895
- return currentColorSpace;
896
- }
897
1034
  function formatNum(n, precision) {
898
1035
  return parseFloat(n.toFixed(precision)).toString();
899
1036
  }
@@ -1150,6 +1287,91 @@ function strToColorSpace(color) {
1150
1287
  return result;
1151
1288
  }
1152
1289
  /**
1290
+ * Set the alpha of a whole color, replacing any it already carries.
1291
+ *
1292
+ * CSS relative color syntax is how opacity is applied to every color Tasty
1293
+ * emits. `oklch(from <color> l c h / <alpha>)` copies the channels over and
1294
+ * writes the alpha slot, which needs nothing from the color except that it *is*
1295
+ * a color: a `color-mix()`, a `light-dark()`, a `currentcolor`, a
1296
+ * `--name-color` written by hand-authored CSS with no companion variable — all
1297
+ * of them work, where writing into a channel slot directly requires components
1298
+ * the engine may have no way to compute.
1299
+ *
1300
+ * Alpha is *replaced*, not composed. A token holding `rgb(255 0 0 / .8)` faded
1301
+ * to `.5` is alpha `.5`, matching what the channel-components form did and what
1302
+ * a statically-known color still does. `color-mix()` against `transparent`
1303
+ * cannot do this — it would multiply the two to `.4`.
1304
+ *
1305
+ * The alpha slot takes a `<number>` or a `<percentage>`, so an opacity custom
1306
+ * property passes straight through in whichever form the author declared it.
1307
+ *
1308
+ * The space is always `oklch`, regardless of the configured {@link ColorSpace}:
1309
+ * it is unbounded, so a wide-gamut color survives the round trip that a
1310
+ * gamut-limited space would clamp, and it is the space the computed value used
1311
+ * to be reported in. Channels are copied rather than interpolated, so the polar
1312
+ * form costs nothing even for an achromatic color, whose hue is carried through
1313
+ * untouched.
1314
+ */
1315
+ function overrideColorAlpha(color, alpha) {
1316
+ return `oklch(from ${color} l c h / ${alpha})`;
1317
+ }
1318
+ /**
1319
+ * Compose an alpha onto a color, multiplying with any it already carries.
1320
+ *
1321
+ * This is the counterpart to {@link overrideColorAlpha}, and the difference is
1322
+ * the point. A design token names a color, so fading it *sets* its alpha. But
1323
+ * `currentcolor` is the color an element **inherits**, which an ancestor may
1324
+ * already have faded — `#current.4` there means "40% of what reaches me", and a
1325
+ * nested `#current.18` under it composes to `.072`. Design systems build ramps
1326
+ * out of exactly that, so `#current` composes and a token replaces.
1327
+ *
1328
+ * `color-mix()` against `transparent` is what composes: mixing premultiplied
1329
+ * leaves the channels alone and multiplies the alphas. Its percentage slot takes
1330
+ * no `<number>`, so a `$prop` alpha has to be scaled — which is why an opacity
1331
+ * property used as `#current.$prop` has to hold a unitless number, where a token
1332
+ * accepts either form.
1333
+ */
1334
+ function mixColorAlpha(color, percentage) {
1335
+ return `color-mix(in oklab, ${color} ${percentage}, transparent)`;
1336
+ }
1337
+ /**
1338
+ * Matches what {@link overrideColorAlpha} builds. The first group is greedy so a
1339
+ * nested override splits on its outermost layer.
1340
+ */
1341
+ const RE_ALPHA_OVERRIDE = /^oklch\(from (.+) l c h \/ (.+)\)$/;
1342
+ /**
1343
+ * Split what {@link overrideColorAlpha} builds back into the color it faded and
1344
+ * the alpha, or `null` when the value is not one of those.
1345
+ */
1346
+ function parseAlphaOverride(value) {
1347
+ const match = value.match(RE_ALPHA_OVERRIDE);
1348
+ return match ? {
1349
+ color: match[1],
1350
+ alpha: match[2]
1351
+ } : null;
1352
+ }
1353
+ /** Channel names of each color space, in `<func>()` argument order. */
1354
+ const COLOR_SPACE_CHANNELS = {
1355
+ rgb: "r g b",
1356
+ hsl: "h s l",
1357
+ oklch: "l c h"
1358
+ };
1359
+ /**
1360
+ * Express a color as components of the configured color space *by reference*,
1361
+ * using CSS relative color syntax: `from <color> l c h`.
1362
+ *
1363
+ * Static colors are decomposed into numbers (see `getColorSpaceComponents`), but
1364
+ * a color the engine cannot evaluate at build time — a `color-mix()`, a
1365
+ * `light-dark()`, a `color()` in a space with no conversion, anything reached
1366
+ * through `var()` — has no numbers to decompose. Handing back the relative form
1367
+ * keeps the `--name-color-{space}` companion usable anyway: the browser resolves
1368
+ * the channels, so `oklch(var(--name-color-oklch) / .5)` still applies opacity to
1369
+ * whatever the color turns out to be.
1370
+ */
1371
+ function toRelativeColorSpaceComponents(color) {
1372
+ return `from ${color} ${COLOR_SPACE_CHANNELS[currentColorSpace]}`;
1373
+ }
1374
+ /**
1153
1375
  * Extract the decomposed components of a color in the configured color space.
1154
1376
  * Returns a space-separated string of components without the wrapping function.
1155
1377
  * Alpha is NOT included — components are used for alpha composition via `/ alpha`.
@@ -1170,6 +1392,41 @@ function getColorSpaceComponents(color) {
1170
1392
  return result;
1171
1393
  }
1172
1394
  /**
1395
+ * Rewrite a color into the `--name-color-{space}` components that `#name.alpha`
1396
+ * composes opacity onto.
1397
+ *
1398
+ * A `var()` chain is rewritten reference by reference so the fallback order
1399
+ * survives; a color the engine can evaluate is decomposed into numbers; anything
1400
+ * left — a derived color function, a `color()` in a space with no conversion —
1401
+ * falls back to relative color syntax and lets the browser do it.
1402
+ *
1403
+ * Returns the input unchanged when there is nothing to rewrite, so callers can
1404
+ * tell whether the conversion found anything.
1405
+ *
1406
+ * Example: `var(--primary-color, var(--secondary-color))`
1407
+ * → `var(--primary-color-oklch, var(--secondary-color-oklch))`
1408
+ */
1409
+ function convertColorChainToComponentChain(colorValue) {
1410
+ const suffix = getColorSpaceSuffix();
1411
+ const faded = parseAlphaOverride(colorValue);
1412
+ if (faded) return convertColorChainToComponentChain(faded.color);
1413
+ const componentVarMatch = colorValue.match(/^(?:rgb|hsl|oklch)a?\(\s*(var\(--[a-z0-9-]+-color-(?:rgb|hsl|oklch)\))\s*\//);
1414
+ if (componentVarMatch) return componentVarMatch[1];
1415
+ if (colorFuncName(colorValue)) {
1416
+ const components = getColorSpaceComponents(colorValue);
1417
+ return components !== colorValue ? components : toRelativeColorSpaceComponents(colorValue);
1418
+ }
1419
+ const match = colorValue.match(/var\(--([a-z0-9-]+)-color\s*(?:,\s*(.+))?\)/);
1420
+ if (!match) {
1421
+ const components = getColorSpaceComponents(colorValue);
1422
+ if (components !== colorValue) return components;
1423
+ return colorValue;
1424
+ }
1425
+ const [, name, fallback] = match;
1426
+ if (!fallback) return `var(--${name}-color-${suffix})`;
1427
+ return `var(--${name}-color-${suffix}, ${convertColorChainToComponentChain(fallback.trim())})`;
1428
+ }
1429
+ /**
1173
1430
  * Convert a color initial value (from @property definitions) to components
1174
1431
  * in the configured color space.
1175
1432
  */
@@ -1203,63 +1460,6 @@ function getComponentPropertySyntax() {
1203
1460
  }
1204
1461
  }
1205
1462
  //#endregion
1206
- //#region src/parser/const.ts
1207
- const VALUE_KEYWORDS = new Set([
1208
- "auto",
1209
- "max-content",
1210
- "min-content",
1211
- "fit-content",
1212
- "stretch",
1213
- "initial",
1214
- "inherit",
1215
- "revert",
1216
- "unset",
1217
- "revert-layer"
1218
- ]);
1219
- const CSS_WIDE_KEYWORDS = new Set([
1220
- "initial",
1221
- "inherit",
1222
- "revert",
1223
- "unset",
1224
- "revert-layer"
1225
- ]);
1226
- const COLOR_FUNCS = new Set([
1227
- "rgb",
1228
- "rgba",
1229
- "hsl",
1230
- "hsla",
1231
- "hwb",
1232
- "lab",
1233
- "lch",
1234
- "oklab",
1235
- "oklch",
1236
- "color",
1237
- "device-cmyk",
1238
- "gray",
1239
- "color-mix",
1240
- "color-contrast"
1241
- ]);
1242
- const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
1243
- const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
1244
- const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
1245
- const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
1246
- const CANONICAL_FUNC_CASE = new Map([
1247
- ["translatex", "translateX"],
1248
- ["translatey", "translateY"],
1249
- ["translatez", "translateZ"],
1250
- ["scalex", "scaleX"],
1251
- ["scaley", "scaleY"],
1252
- ["scalez", "scaleZ"],
1253
- ["rotatex", "rotateX"],
1254
- ["rotatey", "rotateY"],
1255
- ["rotatez", "rotateZ"],
1256
- ["skewx", "skewX"],
1257
- ["skewy", "skewY"]
1258
- ]);
1259
- function canonicalFuncName(lowered) {
1260
- return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
1261
- }
1262
- //#endregion
1263
1463
  //#region src/parser/types.ts
1264
1464
  const makeEmptyPart = () => ({
1265
1465
  mods: [],
@@ -1299,6 +1499,53 @@ const finalizeGroup = (d, parts) => {
1299
1499
  //#endregion
1300
1500
  //#region src/parser/classify.ts
1301
1501
  /**
1502
+ * Convert an opacity suffix to the alpha value it denotes.
1503
+ *
1504
+ * The authored digits are kept verbatim — `.07` stays `.07` rather than being
1505
+ * multiplied into `7.000000000000001%` — and a `$prop` suffix passes the
1506
+ * reference straight through, so it works whether the property holds a
1507
+ * `<number>` or a `<percentage>`. Both are what the alpha slot accepts.
1508
+ */
1509
+ function alphaSuffixToAlpha(rawAlpha) {
1510
+ if (rawAlpha.startsWith("$")) return `var(--${rawAlpha.slice(1)})`;
1511
+ if (rawAlpha === "0") return "0";
1512
+ return `.${rawAlpha}`;
1513
+ }
1514
+ /** Apply an opacity suffix to a color, replacing any alpha it already carries. */
1515
+ function fadeColor(color, rawAlpha) {
1516
+ return overrideColorAlpha(color, alphaSuffixToAlpha(rawAlpha));
1517
+ }
1518
+ /**
1519
+ * Convert an opacity suffix to the percentage `color-mix()` needs, by shifting
1520
+ * the decimal point rather than multiplying: `.07` is `7%`, not the
1521
+ * `7.000000000000001%` that `parseFloat('.07') * 100` produces.
1522
+ */
1523
+ function alphaSuffixToPercentage(rawAlpha) {
1524
+ if (rawAlpha.startsWith("$")) return `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
1525
+ if (rawAlpha === "0") return "0%";
1526
+ const digits = rawAlpha.length === 1 ? `${rawAlpha}0` : rawAlpha;
1527
+ const whole = String(Number(digits.slice(0, 2)));
1528
+ const fraction = digits.slice(2);
1529
+ return `${fraction ? `${whole}.${fraction}` : whole}%`;
1530
+ }
1531
+ /**
1532
+ * Apply an opacity suffix to `currentcolor`, composing with any alpha an
1533
+ * ancestor already applied. See {@link mixColorAlpha} for why this differs from
1534
+ * how a token is faded.
1535
+ */
1536
+ function fadeCurrentColor(rawAlpha) {
1537
+ return mixColorAlpha("currentcolor", alphaSuffixToPercentage(rawAlpha));
1538
+ }
1539
+ /**
1540
+ * Whether parsed function arguments hold a color. Colors reach either the color
1541
+ * bucket (`#token`, a nested color function, `transparent`) or — for the CSS
1542
+ * named colors, which the parser has no token syntax for — the modifier bucket.
1543
+ */
1544
+ function hasColorArgs(parsed) {
1545
+ const namedColors = getNamedColorHex();
1546
+ return parsed.groups.some((group) => group.colors.length > 0 || group.mods.some((mod) => namedColors.has(mod)));
1547
+ }
1548
+ /**
1302
1549
  * Re-parses a value through the parser until it stabilizes (no changes)
1303
1550
  * or max iterations reached. This allows units to reference other units.
1304
1551
  * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'
@@ -1400,35 +1647,34 @@ function classify(raw, opts, recurse) {
1400
1647
  processed: "currentcolor"
1401
1648
  };
1402
1649
  const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1403
- if (currentAlphaMatch) {
1404
- const rawAlpha = currentAlphaMatch[1];
1405
- let percentage;
1406
- if (rawAlpha.startsWith("$")) percentage = `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
1407
- else if (rawAlpha === "0") percentage = "0%";
1408
- else percentage = `${parseFloat("." + rawAlpha) * 100}%`;
1409
- return {
1410
- bucket: 0,
1411
- processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`
1412
- };
1413
- }
1650
+ if (currentAlphaMatch) return {
1651
+ bucket: 0,
1652
+ processed: fadeCurrentColor(currentAlphaMatch[1])
1653
+ };
1414
1654
  if (token[0] === "$" || token[0] === "#") {
1415
1655
  const predefinedTokens = getGlobalPredefinedTokens();
1416
1656
  if (predefinedTokens) {
1417
- if (token in predefinedTokens) {
1418
- const tokenValue = predefinedTokens[token];
1419
- return classify(tokenValue.toLowerCase(), opts, recurse);
1657
+ const lookupKey = token.toLowerCase();
1658
+ if (lookupKey in predefinedTokens) {
1659
+ const tokenValue = predefinedTokens[lookupKey];
1660
+ return classify(foldDslCase(tokenValue), opts, recurse);
1420
1661
  }
1421
1662
  if (token[0] === "#") {
1422
1663
  const alphaMatch = token.match(/^(#[a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1423
1664
  if (alphaMatch) {
1424
1665
  const [, baseToken, rawAlpha] = alphaMatch;
1425
- if (baseToken in predefinedTokens) {
1426
- const resolvedValue = predefinedTokens[baseToken];
1427
- if (resolvedValue.startsWith("#")) return classify(`${resolvedValue.toLowerCase()}.${rawAlpha}`, opts, recurse);
1428
- const funcMatch = resolvedValue.match(/^([a-z][a-z0-9-]*)\((.+)\)$/i);
1666
+ const baseKey = baseToken.toLowerCase();
1667
+ if (baseKey in predefinedTokens) {
1668
+ const resolvedValue = predefinedTokens[baseKey];
1669
+ if (resolvedValue.startsWith("#")) return classify(`${foldDslCase(resolvedValue)}.${rawAlpha}`, opts, recurse);
1670
+ const funcMatch = resolvedValue.match(RE_FUNC_CALL);
1429
1671
  if (funcMatch) {
1430
1672
  const [, funcName, args] = funcMatch;
1431
1673
  const lowerFunc = funcName.toLowerCase();
1674
+ if (DERIVED_COLOR_FUNCS.has(lowerFunc)) return {
1675
+ bucket: 0,
1676
+ processed: fadeColor(classify(foldDslCase(resolvedValue), opts, recurse).processed, rawAlpha)
1677
+ };
1432
1678
  const isCustomFunc = !!(opts.functions && lowerFunc in opts.functions && !COLOR_FUNCS.has(lowerFunc) && !COLOR_FUNCS.has(funcName.replace(/a$/i, "").toLowerCase()));
1433
1679
  const normalizedFunc = isCustomFunc ? lowerFunc : funcName.replace(/a$/i, "").toLowerCase();
1434
1680
  if (!(COLOR_FUNCS.has(normalizedFunc) || COLOR_FUNCS.has(lowerFunc) || isCustomFunc)) return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
@@ -1493,7 +1739,7 @@ function classify(raw, opts, recurse) {
1493
1739
  }
1494
1740
  }
1495
1741
  }
1496
- if (token.match(/^var\(--([a-z0-9-]+)-color\)$/)) return {
1742
+ if (token.match(/^var\(--([a-zA-Z0-9-]+)-color\)$/)) return {
1497
1743
  bucket: 0,
1498
1744
  processed: token
1499
1745
  };
@@ -1502,12 +1748,12 @@ function classify(raw, opts, recurse) {
1502
1748
  processed: token
1503
1749
  };
1504
1750
  if (token[0] === "$") {
1505
- const identMatch = token.match(/^\$([a-z_][a-z0-9-_]*)$/);
1751
+ const identMatch = token.match(/^\$([a-z_][a-zA-Z0-9-_]*)$/);
1506
1752
  if (identMatch) {
1507
1753
  const name = identMatch[1];
1508
1754
  const processed = `var(--${name})`;
1509
1755
  return {
1510
- bucket: name.endsWith("-color") ? 0 : 1,
1756
+ bucket: name.endsWith("-color") ? 3 : 1,
1511
1757
  processed
1512
1758
  };
1513
1759
  }
@@ -1516,13 +1762,9 @@ function classify(raw, opts, recurse) {
1516
1762
  const alphaMatch = token.match(/^#([a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1517
1763
  if (alphaMatch) {
1518
1764
  const [, base, rawAlpha] = alphaMatch;
1519
- let alpha;
1520
- if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
1521
- else if (rawAlpha === "0") alpha = "0";
1522
- else alpha = `.${rawAlpha}`;
1523
1765
  return {
1524
1766
  bucket: 0,
1525
- processed: `${getColorSpaceFunc()}(var(--${base}-color-${getColorSpaceSuffix()}) / ${alpha})`
1767
+ processed: fadeColor(`var(--${base}-color)`, rawAlpha)
1526
1768
  };
1527
1769
  }
1528
1770
  const name = token.slice(1);
@@ -1540,10 +1782,12 @@ function classify(raw, opts, recurse) {
1540
1782
  const fname = token.slice(0, openIdx);
1541
1783
  const inner = token.slice(openIdx + 1, -1);
1542
1784
  if (COLOR_FUNCS.has(fname)) {
1543
- const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
1785
+ const parsedInner = recurse(inner);
1786
+ const argProcessed = parsedInner.output.replace(/,\s+/g, ",");
1787
+ const processed = `${canonicalFuncName(fname)}(${argProcessed})`;
1544
1788
  return {
1545
- bucket: 0,
1546
- processed: `${canonicalFuncName(fname)}(${argProcessed})`
1789
+ bucket: fname === "light-dark" && !hasColorArgs(parsedInner) ? 1 : 0,
1790
+ processed
1547
1791
  };
1548
1792
  }
1549
1793
  if (opts.functions && fname in opts.functions) {
@@ -1570,12 +1814,12 @@ function classify(raw, opts, recurse) {
1570
1814
  }
1571
1815
  }
1572
1816
  if (token.startsWith("(") && token.endsWith(")")) {
1573
- const match = token.slice(1, -1).match(/^\$([a-z_][a-z0-9-_]*)\s*,\s*(.*)$/);
1817
+ const match = token.slice(1, -1).match(/^\$([a-z_][a-zA-Z0-9-_]*)\s*,\s*(.*)$/);
1574
1818
  if (match) {
1575
1819
  const [, name, fallback] = match;
1576
1820
  const processedFallback = recurse(fallback).output;
1577
1821
  return {
1578
- bucket: name.endsWith("-color") ? 0 : 1,
1822
+ bucket: name.endsWith("-color") ? 3 : 1,
1579
1823
  processed: `var(--${name}, ${processedFallback})`
1580
1824
  };
1581
1825
  }
@@ -1712,7 +1956,7 @@ var StyleParser = class {
1712
1956
  const key = String(src);
1713
1957
  const hit = this.cache.get(key);
1714
1958
  if (hit) return hit;
1715
- const stripped = src.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, "").toLowerCase();
1959
+ const stripped = foldDslCase(src.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, ""));
1716
1960
  const groups = [];
1717
1961
  let currentGroup = makeEmptyDetails();
1718
1962
  let currentPart = makeEmptyPart();
@@ -1739,6 +1983,10 @@ var StyleParser = class {
1739
1983
  case 2:
1740
1984
  currentPart.mods.push(processed);
1741
1985
  break;
1986
+ case 3:
1987
+ currentPart.colors.push(processed);
1988
+ currentPart.values.push(processed);
1989
+ break;
1742
1990
  }
1743
1991
  currentPart.all.push(processed);
1744
1992
  };
@@ -2015,8 +2263,8 @@ function normalizeColorTokenValue(value) {
2015
2263
  if (value === false) return null;
2016
2264
  return value;
2017
2265
  }
2018
- const COLOR_VAR_PATTERN = /var\(--([a-z0-9-]+)-color/;
2019
- const COLOR_VAR_COMPONENTS_PATTERN = /var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;
2266
+ const COLOR_VAR_PATTERN = /^var\(--([a-z0-9-]+)-color[,)]/;
2267
+ const COLOR_VAR_COMPONENTS_PATTERN = /^(?:[a-z-]+\(\s*)?var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)[,)]/;
2020
2268
  const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
2021
2269
  const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
2022
2270
  const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
@@ -2161,6 +2409,28 @@ function parseStyle(value) {
2161
2409
  else str = "";
2162
2410
  return getOrCreateParser().process(str);
2163
2411
  }
2412
+ /** A `$`-prefixed custom-property reference — the only DSL syntax that starts with `$`. */
2413
+ const RE_CUSTOM_PROPERTY_REF = /\$[a-zA-Z_$]/;
2414
+ /**
2415
+ * Substitute custom-property references (`$ident` → `var(--ident)`) inside a
2416
+ * value a handler would otherwise emit to CSS verbatim.
2417
+ *
2418
+ * Handlers for keyword-valued properties (`display`, `align`, `text-transform`,
2419
+ * `overflow`, …) pass their input straight through, since there are no units or
2420
+ * color tokens to resolve. That leaked the raw DSL into the stylesheet:
2421
+ * `display: '$my-display'` emitted `display: $my-display`, which the browser
2422
+ * drops as invalid. The same applies to color props whose value the parser could
2423
+ * not classify as a color — `$ident` is a color only when the name ends with
2424
+ * `-color`, so `fill: '$my-fill'` fell back to the unparsed input.
2425
+ *
2426
+ * Values without a `$` skip the parser entirely, so pass-through behaviour (and
2427
+ * the parser's case folding) is unchanged for every value that was already
2428
+ * valid CSS.
2429
+ */
2430
+ function resolveCustomProperties(value) {
2431
+ if (!RE_CUSTOM_PROPERTY_REF.test(value)) return value;
2432
+ return parseStyle(value).output || value;
2433
+ }
2164
2434
  /**
2165
2435
  * Parse color. Find it value, name and opacity.
2166
2436
  * Optimized to avoid heavy parseStyle calls for simple color patterns.
@@ -2184,11 +2454,17 @@ function parseColor(val, ignoreError = false) {
2184
2454
  }
2185
2455
  firstColor = extractedColor;
2186
2456
  }
2187
- let nameMatch = firstColor.match(COLOR_VAR_PATTERN);
2188
- if (!nameMatch) nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2457
+ const faded = parseAlphaOverride(firstColor);
2458
+ const baseColor = faded ? faded.color : firstColor;
2459
+ let nameMatch = baseColor.match(COLOR_VAR_PATTERN);
2460
+ if (!nameMatch) nameMatch = baseColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2189
2461
  let opacity;
2190
- if (firstColor.startsWith("rgb") || firstColor.startsWith("hsl") || firstColor.startsWith("lch") || firstColor.startsWith("oklch")) {
2191
- const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);
2462
+ if (faded) {
2463
+ const { alpha } = faded;
2464
+ const v = parseFloat(alpha);
2465
+ if (!isNaN(v)) opacity = alpha.endsWith("%") ? v : v * 100;
2466
+ } else if (baseColor.startsWith("rgb") || baseColor.startsWith("hsl") || baseColor.startsWith("lch") || baseColor.startsWith("oklch")) {
2467
+ const alphaMatch = baseColor.match(RGB_ALPHA_PATTERN);
2192
2468
  if (alphaMatch) {
2193
2469
  const v = parseFloat(alphaMatch[1]);
2194
2470
  if (!isNaN(v)) opacity = v * 100;
@@ -3156,6 +3432,10 @@ function rscClassRegexGlobal(prefix) {
3156
3432
  */
3157
3433
  const CUSTOM_PROP_DECL = /^\s*(--[a-z0-9_-]+)\s*:\s*(.+?)\s*$/i;
3158
3434
  const SINGLE_VAR_REF = /^var\((--[a-z0-9_-]+)\)$/i;
3435
+ /** A `--name-color-{space}` companion holding a color's channel components. */
3436
+ const COLOR_COMPONENTS_DECL = /^\s*(--[a-z0-9_-]+-color-(?:rgb|hsl|oklch))\s*:\s*(.+?)\s*$/i;
3437
+ /** A component list the browser can type as `<number>+`. */
3438
+ const NUMERIC_COMPONENTS = /^[\d\s.+-]+$/;
3159
3439
  var PropertyTypeResolver = class {
3160
3440
  /** propName → the prop it depends on */
3161
3441
  pendingDeps = /* @__PURE__ */ new Map();
@@ -3168,6 +3448,15 @@ var PropertyTypeResolver = class {
3168
3448
  scanDeclarations(declarations, isPropertyDefined, registerProperty) {
3169
3449
  if (!declarations.includes("--")) return;
3170
3450
  const parts = declarations.split(/;+/);
3451
+ for (const part of parts) {
3452
+ const match = COLOR_COMPONENTS_DECL.exec(part);
3453
+ if (!match) continue;
3454
+ const propName = match[1];
3455
+ if (isPropertyDefined(propName)) continue;
3456
+ if (NUMERIC_COMPONENTS.test(match[2])) continue;
3457
+ if (isPropertyDefined(propName.slice(0, propName.lastIndexOf("-")))) continue;
3458
+ registerProperty(propName, "*", getDefaultComponents());
3459
+ }
3171
3460
  for (const part of parts) {
3172
3461
  if (!part.trim()) continue;
3173
3462
  const match = CUSTOM_PROP_DECL.exec(part);
@@ -3259,8 +3548,59 @@ function extractCSSWideKeyword(group) {
3259
3548
  if (group.values.length !== 1 || group.colors.length > 0) return null;
3260
3549
  return CSS_WIDE_KEYWORDS.has(group.values[0]) ? group.values[0] : null;
3261
3550
  }
3551
+ /** A resolved custom-property reference, i.e. `var(--name)` or `var(--name, …)`. */
3552
+ const RE_VAR_REFERENCE = /^var\(/;
3553
+ /**
3554
+ * Assign custom-property references to the style and color slots of a
3555
+ * `<line-width> <line-style> <line-color>` shorthand (`border`, `outline`).
3556
+ *
3557
+ * The parser cannot type a reference, so it buckets `$name` as a plain value and
3558
+ * the handler has to place it. A reference fills the first slot still free, in
3559
+ * shorthand order: the width takes `values[0]`, then the style, then the color.
3560
+ * The style would otherwise arrive as a keyword (`solid`, `dashed`, …) that a
3561
+ * reference has no way to match, and the color as `#name` — the `$name-color`
3562
+ * form the parser buckets as a color exists to reference a raw CSS custom
3563
+ * property, not as the way colors are written, so a reference should not reach
3564
+ * for the color slot until the style slot is taken.
3565
+ *
3566
+ * Lengths are left alone: a second length is not valid in these shorthands, and
3567
+ * promoting one would emit an invalid declaration instead of ignoring an extra
3568
+ * value. Callers supply their own slot defaults.
3569
+ */
3570
+ function assignLineSlots(values, styleKeyword, colorToken) {
3571
+ if (styleKeyword && colorToken) return {
3572
+ style: styleKeyword,
3573
+ color: colorToken
3574
+ };
3575
+ const spare = values.slice(1).filter((value) => RE_VAR_REFERENCE.test(value) && value !== colorToken);
3576
+ return {
3577
+ style: styleKeyword || spare.shift(),
3578
+ color: colorToken || spare.shift()
3579
+ };
3580
+ }
3262
3581
  /** Warning keys already emitted, so each distinct offending value warns once. */
3263
3582
  const emittedWarnings$2 = /* @__PURE__ */ new Set();
3583
+ /** A resolved custom-property reference anywhere in a token. */
3584
+ const RE_HAS_VAR_REFERENCE = /var\(/;
3585
+ /**
3586
+ * Reject a custom-property reference used where a *token name* is expected.
3587
+ *
3588
+ * `preset` and `transition` take the name of a design token (`t3`, `fill`) and
3589
+ * interpolate it into a CSS custom property — `var(--t3-font-size)`. A reference
3590
+ * substituted into that position builds `var(--var(--x)-font-size)`, which is not
3591
+ * a valid custom-property name, so the browser drops the declaration and the
3592
+ * style silently does nothing. The DSL has no way to indirect a token name
3593
+ * through a custom property: the name is needed at build time, and a reference
3594
+ * only resolves in the browser.
3595
+ *
3596
+ * Returns true (warning once per value in dev) so the caller can fall back
3597
+ * instead of emitting a declaration that cannot work.
3598
+ */
3599
+ function isTokenNameReference(property, name) {
3600
+ if (!RE_HAS_VAR_REFERENCE.test(name)) return false;
3601
+ warnOnceDev(`token-name-reference:${property}:${name}`, `${property}="${name}": a custom property cannot name a token. The name is interpolated into a CSS custom property at build time, so a reference would build an unusable name. It is ignored.`);
3602
+ return true;
3603
+ }
3264
3604
  /**
3265
3605
  * Emit a style-level warning at most once per `key`. No-op outside dev mode.
3266
3606
  *
@@ -3314,13 +3654,13 @@ function warnExtraGroupValues(property, input, maxValues) {
3314
3654
  function processGroup$1(group) {
3315
3655
  const { values, mods, colors } = group;
3316
3656
  const directions = filterMods(mods, DIRECTIONS);
3317
- const typeMods = filterMods(mods, BORDER_STYLES);
3657
+ const slots = assignLineSlots(values, filterMods(mods, BORDER_STYLES)[0], colors?.[0]);
3318
3658
  return {
3319
3659
  directions,
3320
3660
  borderValue: {
3321
3661
  width: values[0] || "var(--border-width)",
3322
- style: typeMods[0] || "solid",
3323
- color: colors?.[0] || "var(--border-color, currentColor)"
3662
+ style: slots.style || "solid",
3663
+ color: slots.color || "var(--border-color, currentColor)"
3324
3664
  }
3325
3665
  };
3326
3666
  }
@@ -3419,32 +3759,28 @@ function borderStyle({ border }) {
3419
3759
  }
3420
3760
  borderStyle.__lookupStyles = ["border"];
3421
3761
  //#endregion
3422
- //#region src/utils/string.ts
3423
- function toSnakeCase(str) {
3424
- return str.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`);
3762
+ //#region src/styles/color.ts
3763
+ function colorStyle({ color }) {
3764
+ if (!color) return null;
3765
+ if (color === true) color = "currentColor";
3766
+ color = parseColor(color, true).color || resolveCustomProperties(color);
3767
+ const match = color.match(/var\(--(.+?)-color/);
3768
+ let name = "";
3769
+ if (match) name = match[1];
3770
+ const styles = { color };
3771
+ if (name && name !== "current") {
3772
+ const suffix = getColorSpaceSuffix();
3773
+ Object.assign(styles, {
3774
+ "--current-color": color,
3775
+ [`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
3776
+ });
3777
+ }
3778
+ return styles;
3425
3779
  }
3780
+ colorStyle.__lookupStyles = ["color"];
3426
3781
  //#endregion
3427
3782
  //#region src/styles/createStyle.ts
3428
3783
  const CACHE = {};
3429
- /**
3430
- * Convert color fallback chain to component fallback chain.
3431
- * Example: var(--primary-color, var(--secondary-color))
3432
- * → var(--primary-color-oklch, var(--secondary-color-oklch))
3433
- */
3434
- function convertColorChainToComponentChain(colorValue) {
3435
- const suffix = getColorSpaceSuffix();
3436
- const componentVarMatch = colorValue.match(/^(?:rgb|hsl|oklch)a?\(\s*(var\(--[a-z0-9-]+-color-(?:rgb|hsl|oklch)\))\s*\//);
3437
- if (componentVarMatch) return componentVarMatch[1];
3438
- const match = colorValue.match(/var\(--([a-z0-9-]+)-color\s*(?:,\s*(.+))?\)/);
3439
- if (!match) {
3440
- const components = getColorSpaceComponents(colorValue);
3441
- if (components !== colorValue) return components;
3442
- return colorValue;
3443
- }
3444
- const [, name, fallback] = match;
3445
- if (!fallback) return `var(--${name}-color-${suffix})`;
3446
- return `var(--${name}-color-${suffix}, ${convertColorChainToComponentChain(fallback.trim())})`;
3447
- }
3448
3784
  function createStyle(styleName, cssStyle, converter) {
3449
3785
  const key = `${styleName}.${cssStyle ?? ""}`;
3450
3786
  if (!CACHE[key]) {
@@ -3453,16 +3789,18 @@ function createStyle(styleName, cssStyle, converter) {
3453
3789
  if (styleValue == null || styleValue === false) return null;
3454
3790
  let finalCssStyle;
3455
3791
  const isColorToken = !cssStyle && typeof styleName === "string" && styleName.startsWith("#");
3456
- if (isColorToken) finalCssStyle = `--${toSnakeCase(styleName.slice(1)).replace(/^-+/, "")}-color`;
3457
- else finalCssStyle = cssStyle || toSnakeCase(styleName).replace(/^\$/, "--");
3792
+ if (isColorToken) finalCssStyle = `--${normalizeDslName(styleName.slice(1))}-color`;
3793
+ else if (!cssStyle && styleName[0] === "$") finalCssStyle = `--${normalizeDslName(styleName.slice(1))}`;
3794
+ else finalCssStyle = cssStyle || toSnakeCase(styleName);
3458
3795
  if (isColorToken) {
3459
3796
  const normalized = normalizeColorTokenValue(styleValue);
3460
3797
  if (normalized === null) return null;
3461
3798
  styleValue = normalized;
3462
3799
  }
3463
- if (converter && typeof styleValue !== "string") {
3464
- styleValue = converter(styleValue);
3465
- if (!styleValue) return null;
3800
+ if (converter) {
3801
+ const converted = converter(styleValue);
3802
+ if (converted) styleValue = converted;
3803
+ else if (typeof styleValue !== "string") return null;
3466
3804
  }
3467
3805
  if (typeof styleValue === "string" && finalCssStyle.startsWith("--") && finalCssStyle.endsWith("-color")) {
3468
3806
  styleValue = styleValue.trim();
@@ -3486,6 +3824,10 @@ function createStyle(styleName, cssStyle, converter) {
3486
3824
  [finalCssStyle]: colorSpaceStr,
3487
3825
  [`${finalCssStyle}-${suffix}`]: getColorSpaceComponents(colorSpaceStr)
3488
3826
  };
3827
+ if (color && colorFuncName(color)) return {
3828
+ [finalCssStyle]: color,
3829
+ [`${finalCssStyle}-${suffix}`]: convertColorChainToComponentChain(color)
3830
+ };
3489
3831
  return { [finalCssStyle]: color ?? "" };
3490
3832
  }
3491
3833
  const processed = parseStyle(styleValue);
@@ -3497,26 +3839,6 @@ function createStyle(styleName, cssStyle, converter) {
3497
3839
  return CACHE[key];
3498
3840
  }
3499
3841
  //#endregion
3500
- //#region src/styles/color.ts
3501
- function colorStyle({ color }) {
3502
- if (!color) return null;
3503
- if (color === true) color = "currentColor";
3504
- if (typeof color === "string" && (color.startsWith("#") || color.startsWith("(#"))) color = parseColor(color).color || color;
3505
- const match = color.match(/var\(--(.+?)-color/);
3506
- let name = "";
3507
- if (match) name = match[1];
3508
- const styles = { color };
3509
- if (name && name !== "current") {
3510
- const suffix = getColorSpaceSuffix();
3511
- Object.assign(styles, {
3512
- "--current-color": color,
3513
- [`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
3514
- });
3515
- }
3516
- return styles;
3517
- }
3518
- colorStyle.__lookupStyles = ["color"];
3519
- //#endregion
3520
3842
  //#region src/styles/display.ts
3521
3843
  /**
3522
3844
  * Process textOverflow into CSS properties for truncation/clamping.
@@ -3567,14 +3889,15 @@ function processTextOverflow(textOverflow, whiteSpace) {
3567
3889
  */
3568
3890
  function displayStyle({ display, hide, textOverflow, overflow, whiteSpace }) {
3569
3891
  const result = {};
3892
+ const whiteSpaceValue = whiteSpace ? resolveCustomProperties(whiteSpace) : whiteSpace;
3570
3893
  if (textOverflow != null && textOverflow !== false) {
3571
- const textResult = processTextOverflow(textOverflow, whiteSpace);
3894
+ const textResult = processTextOverflow(textOverflow, whiteSpaceValue);
3572
3895
  if (textResult) Object.assign(result, textResult);
3573
3896
  }
3574
- if (overflow && !result["overflow"]) result["overflow"] = overflow;
3575
- if (whiteSpace && !result["white-space"]) result["white-space"] = whiteSpace;
3897
+ if (overflow && !result["overflow"]) result["overflow"] = resolveCustomProperties(overflow);
3898
+ if (whiteSpaceValue && !result["white-space"]) result["white-space"] = whiteSpaceValue;
3576
3899
  if (hide) result["display"] = "none";
3577
- else if (!result["display"] && display) result["display"] = display;
3900
+ else if (!result["display"] && display) result["display"] = resolveCustomProperties(display);
3578
3901
  if (Object.keys(result).length === 0) return null;
3579
3902
  return result;
3580
3903
  }
@@ -3655,7 +3978,7 @@ function fillStyle({ fill, backgroundColor, image, backgroundImage, backgroundPo
3655
3978
  const parsed = parseStyle(colorValue);
3656
3979
  const firstColor = parsed.groups[0]?.colors[0];
3657
3980
  const secondColor = parsed.groups[0]?.colors[1];
3658
- result["background-color"] = firstColor || colorValue;
3981
+ result["background-color"] = firstColor || resolveCustomProperties(colorValue);
3659
3982
  if (secondColor) result["--tasty-second-fill-color"] = secondColor;
3660
3983
  }
3661
3984
  const gradientLayer = result["--tasty-second-fill-color"] ? "linear-gradient(var(--tasty-second-fill-color), var(--tasty-second-fill-color))" : null;
@@ -3666,10 +3989,10 @@ function fillStyle({ fill, backgroundColor, image, backgroundImage, backgroundPo
3666
3989
  } else if (gradientLayer) result["background-image"] = gradientLayer;
3667
3990
  if (backgroundPosition) result["background-position"] = parseStyle(backgroundPosition).output || backgroundPosition;
3668
3991
  if (backgroundSize) result["background-size"] = parseStyle(backgroundSize).output || backgroundSize;
3669
- if (backgroundRepeat) result["background-repeat"] = backgroundRepeat;
3670
- if (backgroundAttachment) result["background-attachment"] = backgroundAttachment;
3671
- if (backgroundOrigin) result["background-origin"] = backgroundOrigin;
3672
- if (backgroundClip) result["background-clip"] = backgroundClip;
3992
+ if (backgroundRepeat) result["background-repeat"] = resolveCustomProperties(backgroundRepeat);
3993
+ if (backgroundAttachment) result["background-attachment"] = resolveCustomProperties(backgroundAttachment);
3994
+ if (backgroundOrigin) result["background-origin"] = resolveCustomProperties(backgroundOrigin);
3995
+ if (backgroundClip) result["background-clip"] = resolveCustomProperties(backgroundClip);
3673
3996
  if (Object.keys(result).length === 0) return null;
3674
3997
  return result;
3675
3998
  }
@@ -3688,7 +4011,7 @@ fillStyle.__lookupStyles = [
3688
4011
  ];
3689
4012
  function svgFillStyle({ svgFill }) {
3690
4013
  if (!svgFill) return null;
3691
- svgFill = parseStyle(svgFill).groups[0]?.colors[0] || svgFill;
4014
+ svgFill = parseStyle(svgFill).groups[0]?.colors[0] || resolveCustomProperties(svgFill);
3692
4015
  return { fill: svgFill };
3693
4016
  }
3694
4017
  svgFillStyle.__lookupStyles = ["svgFill"];
@@ -3698,7 +4021,7 @@ function flowStyle({ display = "block", flow }) {
3698
4021
  let style;
3699
4022
  if (display.includes("grid")) style = "grid-auto-flow";
3700
4023
  else if (display.includes("flex")) style = "flex-flow";
3701
- return style ? { [style]: flow } : null;
4024
+ return style && flow ? { [style]: resolveCustomProperties(flow) } : null;
3702
4025
  }
3703
4026
  flowStyle.__lookupStyles = ["display", "flow"];
3704
4027
  //#endregion
@@ -4083,17 +4406,18 @@ function outlineStyle({ outline, outlineOffset }) {
4083
4406
  };
4084
4407
  const offsetPart = parts[1];
4085
4408
  const typeMods = filterMods(outlinePart.mods, BORDER_STYLES);
4409
+ const slots = assignLineSlots(outlinePart.values, typeMods[0], outlinePart.colors[0]);
4086
4410
  result["outline"] = [
4087
4411
  outlinePart.values[0] || "var(--outline-width)",
4088
- typeMods[0] || "solid",
4089
- outlinePart.colors[0] || "var(--outline-color)"
4412
+ slots.style || "solid",
4413
+ slots.color || "var(--outline-color)"
4090
4414
  ].join(" ");
4091
4415
  if (offsetPart?.values[0]) result["outline-offset"] = offsetPart.values[0];
4092
4416
  }
4093
4417
  }
4094
4418
  if (outlineOffset != null && !result["outline-offset"]) {
4095
4419
  const offsetValue = typeof outlineOffset === "number" ? `${outlineOffset}px` : outlineOffset;
4096
- result["outline-offset"] = parseStyle(offsetValue).groups[0]?.values[0] || offsetValue;
4420
+ result["outline-offset"] = parseStyle(offsetValue).groups[0]?.values[0] || resolveCustomProperties(offsetValue);
4097
4421
  }
4098
4422
  if (Object.keys(result).length === 0) return null;
4099
4423
  return result;
@@ -4132,7 +4456,7 @@ paddingStyle.__lookupStyles = [
4132
4456
  function str(val) {
4133
4457
  if (val == null || val === false || val === "") return null;
4134
4458
  if (val === true) return "center";
4135
- return String(val);
4459
+ return resolveCustomProperties(String(val));
4136
4460
  }
4137
4461
  /**
4138
4462
  * Unified placement handler replacing align, justify, and place.
@@ -4214,7 +4538,7 @@ const PRESET_MODIFIERS = new Set([
4214
4538
  function toCSS(value, isNumeric) {
4215
4539
  if (value == null) return null;
4216
4540
  if (typeof value === "number") return isNumeric ? `${value}px` : String(value);
4217
- return parseStyle(String(value)).groups[0]?.values[0] || String(value);
4541
+ return parseStyle(String(value)).groups[0]?.values[0] || resolveCustomProperties(String(value));
4218
4542
  }
4219
4543
  function setCSSValue(styles, styleName, presetName, { varOnly, cssOnly } = {}) {
4220
4544
  const value = (() => {
@@ -4236,11 +4560,11 @@ function setCSSValue(styles, styleName, presetName, { varOnly, cssOnly } = {}) {
4236
4560
  * - `fontFamily="Arial"` → Arial (direct, no fallback)
4237
4561
  */
4238
4562
  function resolveFontFamily(font, fontFamily) {
4239
- if (fontFamily) return fontFamily;
4563
+ if (fontFamily) return resolveCustomProperties(fontFamily);
4240
4564
  if (font == null || font === false) return null;
4241
4565
  if (font === "monospace") return "var(--font-mono, var(--font-mono-fallback))";
4242
4566
  if (font === true) return "var(--font-sans, var(--font-sans-fallback))";
4243
- return `${font}, var(--font-sans, var(--font-sans-fallback))`;
4567
+ return `${resolveCustomProperties(font)}, var(--font-sans, var(--font-sans-fallback))`;
4244
4568
  }
4245
4569
  /**
4246
4570
  * Handles typography preset and individual font properties.
@@ -4274,7 +4598,7 @@ function presetStyle({ preset, fontSize, lineHeight, textTransform, letterSpacin
4274
4598
  const nameTokens = namePart?.all ?? [];
4275
4599
  const isModOnly = nameTokens.length > 0 && nameTokens.every((t) => PRESET_MODIFIERS.has(t));
4276
4600
  const nameToken = namePart?.mods[0] ?? namePart?.values[0] ?? "";
4277
- const name = isModOnly ? "inherit" : nameToken || "inherit";
4601
+ const name = isModOnly || isTokenNameReference("preset", nameToken) ? "inherit" : nameToken || "inherit";
4278
4602
  const modTokens = isModOnly ? nameTokens : modPart?.all ?? [];
4279
4603
  const activeMods = /* @__PURE__ */ new Set();
4280
4604
  for (const tok of modTokens) if (PRESET_MODIFIERS.has(tok)) activeMods.add(tok);
@@ -4312,7 +4636,7 @@ function presetStyle({ preset, fontSize, lineHeight, textTransform, letterSpacin
4312
4636
  if (fontStyle != null) if (fontStyle === true) styles["font-style"] = "italic";
4313
4637
  else if (typeof fontStyle === "string" && CSS_WIDE_KEYWORDS.has(fontStyle)) styles["font-style"] = fontStyle;
4314
4638
  else styles["font-style"] = fontStyle ? "italic" : "normal";
4315
- if (textTransform) styles["text-transform"] = textTransform;
4639
+ if (textTransform) styles["text-transform"] = resolveCustomProperties(textTransform);
4316
4640
  const fontFamily_ = resolveFontFamily(font, fontFamily);
4317
4641
  if (fontFamily_) styles["font-family"] = fontFamily_;
4318
4642
  if (Object.keys(styles).length === 0) return null;
@@ -4504,10 +4828,10 @@ function scrollbarStyle({ scrollbar, overflow }) {
4504
4828
  scrollbarStyle.__lookupStyles = ["scrollbar", "overflow"];
4505
4829
  //#endregion
4506
4830
  //#region src/styles/shadow.ts
4507
- function toBoxShadow(shadow) {
4508
- const { values, mods, colors } = parseStyle(shadow).groups[0] ?? makeEmptyDetails();
4831
+ function toBoxShadow(group) {
4832
+ const { values, mods, colors } = group;
4509
4833
  const mod = mods[0] || "";
4510
- const shadowColor = (colors && colors[0]) ?? "";
4834
+ const shadowColor = colors[0] ?? "";
4511
4835
  return [
4512
4836
  mod,
4513
4837
  ...values,
@@ -4518,7 +4842,8 @@ function shadowStyle({ shadow }) {
4518
4842
  if (!shadow) return null;
4519
4843
  if (shadow === true) shadow = "var(--shadow)";
4520
4844
  if (CSS_WIDE_KEYWORDS.has(shadow)) return { "box-shadow": shadow };
4521
- return { "box-shadow": shadow.split(",").map(toBoxShadow).join(",") };
4845
+ const { groups } = parseStyle(shadow);
4846
+ return { "box-shadow": groups.map(toBoxShadow).join(",") };
4522
4847
  }
4523
4848
  shadowStyle.__lookupStyles = ["shadow"];
4524
4849
  //#endregion
@@ -4647,6 +4972,7 @@ function transitionStyle({ transition }) {
4647
4972
  const map = {};
4648
4973
  transitions.forEach((transition) => {
4649
4974
  const name = transition[0];
4975
+ if (isTokenNameReference("transition", name)) return;
4650
4976
  let timing;
4651
4977
  let easing;
4652
4978
  let delay;
@@ -4667,6 +4993,7 @@ function transitionStyle({ transition }) {
4667
4993
  ];
4668
4994
  });
4669
4995
  });
4996
+ if (Object.keys(map).length === 0) return null;
4670
4997
  return { transition: Object.entries(map).map(([style, [name, easing, timing, delay]]) => {
4671
4998
  let value = `${style} ${timing || getTiming(name)}`;
4672
4999
  if (easing || delay) value += ` ${easing || DEFAULT_EASING}`;
@@ -4703,11 +5030,28 @@ const devMode$2 = isDevEnv();
4703
5030
  function inDevMode() {
4704
5031
  return isDevEnv();
4705
5032
  }
5033
+ /**
5034
+ * Read a grid track count, or `undefined` when the value isn't one.
5035
+ *
5036
+ * Plain digit strings count: every value inside a state map arrives as a
5037
+ * string, so `gridColumns: { '': '2', '@media(w < 600px)': '1' }` would
5038
+ * otherwise emit `grid-template-columns: 2` — invalid CSS that browsers drop
5039
+ * silently.
5040
+ *
5041
+ * Zero and negatives yield `undefined`. Zero already produced an empty value,
5042
+ * and a negative number used to throw inside `String.repeat`.
5043
+ */
5044
+ function trackCount(val) {
5045
+ const count = typeof val === "number" ? val : typeof val === "string" && /^\d+$/.test(val.trim()) ? Number(val.trim()) : NaN;
5046
+ return Number.isFinite(count) && count >= 1 ? Math.floor(count) : void 0;
5047
+ }
4706
5048
  const columnsConverter = (val) => {
4707
- if (typeof val === "number") return "minmax(1px, 1fr) ".repeat(val).trim();
5049
+ const count = trackCount(val);
5050
+ return count === void 0 ? void 0 : "minmax(1px, 1fr) ".repeat(count).trim();
4708
5051
  };
4709
5052
  const rowsConverter = (val) => {
4710
- if (typeof val === "number") return "auto ".repeat(val).trim();
5053
+ const count = trackCount(val);
5054
+ return count === void 0 ? void 0 : "auto ".repeat(count).trim();
4711
5055
  };
4712
5056
  const STYLE_HANDLER_MAP$1 = {};
4713
5057
  let initialHandlerMapSnapshot = null;
@@ -4754,7 +5098,7 @@ function predefine() {
4754
5098
  defineStyleAlias("gridRows", "grid-template-rows", rowsConverter);
4755
5099
  defineStyleAlias("gridTemplate", "grid-template", (val) => {
4756
5100
  if (typeof val !== "string") return;
4757
- return val.split("/").map((s, i) => (i ? columnsConverter : rowsConverter)(s)).join("/");
5101
+ return val.split("/").map((s, i) => (i ? columnsConverter : rowsConverter)(s) ?? s).join("/");
4758
5102
  });
4759
5103
  [
4760
5104
  displayStyle,
@@ -12157,6 +12501,6 @@ function resetConfig() {
12157
12501
  delete storage[GLOBAL_INJECTOR_KEY];
12158
12502
  }
12159
12503
  //#endregion
12160
- export { SheetManager as $, getNamedColorHex as $t, FLOW_STYLES as A, registerFunctionPolyfill as At, createStateParserContext as B, stringifyStyles as Bt, BASE_STYLES as C, LAYOUT_CHUNK_STYLES as Ct, COLOR_STYLES as D, formatFunctionRule as Dt, BLOCK_STYLES as E, extractLocalFunctions as Et, hasPipelineCacheEntry as F, getGlobalParser as Ft, StyleInjector as G, createColorFunc as Gt, extractPredefinedStateRefs as H, okhstPlugin as Ht, isSelector as I, getGlobalPredefinedTokens as It, hasLocalCounterStyle as J, colorInitialValueToComponents as Jt, extractLocalCounterStyle as K, isDevEnv as Kt, renderStyles as L, normalizeColorTokenValue as Lt, OUTER_STYLES as M, CUSTOM_UNITS as Mt, POSITION_STYLES as N, DIRECTIONS as Nt, CONTAINER_STYLES as O, hasLocalFunctions as Ot, TEXT_STYLES as P, filterMods as Pt, hasLocalFontFace as Q, getComponentPropertySyntax as Qt, parseStateKey as R, parseColor as Rt, baseStylePropsRegistry as S, FONT_CHUNK_STYLES as St, BLOCK_OUTER_STYLES as T, STYLE_TO_CHUNK as Tt, getGlobalPredefinedStates as U, okhslFunction as Ut, extractLocalPredefinedStates as V, okhstFunction as Vt, setGlobalPredefinedStates as W, okhslPlugin as Wt, fontFaceContentHash as X, getColorSpaceFunc as Xt, extractLocalFontFace as Y, getColorSpaceComponents as Yt, formatFontFaceRule as Z, getColorSpaceSuffix as Zt, isFunctionsPolyfillEnabled as _, propHandlerRegistry as _t, getGlobalCounterStyles as a, Lru as an, DEFAULT_NAME_PREFIX as at, resetConfig as b, DIMENSION_CHUNK_STYLES as bt, getGlobalInjector as c, makeCounterStyleName as ct, getGlobalStyles as d, validateNamePrefix as dt, getRgbValuesFromRgbaString as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, hashString as ft, isConfigLocked as g, parsePropertyToken as gt, hasStylesGenerated as h, hasLocalProperties as ht, getGlobalConfigTokens as i, resolveFunctionColor as in, PropertyTypeResolver as it, INNER_STYLES as j, registerLocalFunctionPolyfills as jt, DIMENSION_STYLES as k, parseFunctionName as kt, getGlobalKeyframes as l, makeKeyframeName as lt, hasGlobalRecipes as m, getEffectiveDefinition as mt, getConfig as n, hslToRgbValues as nn, styleHandlers as nt, getGlobalFontFaces as o, DEFAULT_ZERO_NAME_PREFIX as ot, hasGlobalKeyframes as p, extractLocalProperties as pt, formatCounterStyleRule as q, StyleParser as qt, getEffectiveProperties as r, strToRgb as rn, createStyle as rt, getGlobalFunctions as s, makeClassName as st, configure as t, hexToRgb as tn, defineHandler as tt, getGlobalRecipes as u, tastyClassRegex as ut, isTestEnvironment as v, APPEARANCE_CHUNK_STYLES as vt, BLOCK_INNER_STYLES as w, POSITION_CHUNK_STYLES as wt, generateTypographyTokens as x, DISPLAY_CHUNK_STYLES as xt, markStylesGenerated as y, CHUNK_NAMES as yt, camelToKebab as z, parseStyle as zt };
12504
+ export { SheetManager as $, overrideColorAlpha as $t, FLOW_STYLES as A, registerFunctionPolyfill as At, createStateParserContext as B, stringifyStyles as Bt, BASE_STYLES as C, LAYOUT_CHUNK_STYLES as Ct, COLOR_STYLES as D, formatFunctionRule as Dt, BLOCK_STYLES as E, extractLocalFunctions as Et, hasPipelineCacheEntry as F, getGlobalParser as Ft, StyleInjector as G, createColorFunc as Gt, extractPredefinedStateRefs as H, okhstPlugin as Ht, isSelector as I, getGlobalPredefinedTokens as It, hasLocalCounterStyle as J, colorInitialValueToComponents as Jt, extractLocalCounterStyle as K, isDevEnv as Kt, renderStyles as L, normalizeColorTokenValue as Lt, OUTER_STYLES as M, CUSTOM_UNITS as Mt, POSITION_STYLES as N, DIRECTIONS as Nt, CONTAINER_STYLES as O, hasLocalFunctions as Ot, TEXT_STYLES as P, filterMods as Pt, hasLocalFontFace as Q, getComponentPropertySyntax as Qt, parseStateKey as R, parseColor as Rt, baseStylePropsRegistry as S, FONT_CHUNK_STYLES as St, BLOCK_OUTER_STYLES as T, STYLE_TO_CHUNK as Tt, getGlobalPredefinedStates as U, okhslFunction as Ut, extractLocalPredefinedStates as V, okhstFunction as Vt, setGlobalPredefinedStates as W, okhslPlugin as Wt, fontFaceContentHash as X, getColorSpaceComponents as Xt, extractLocalFontFace as Y, convertColorChainToComponentChain as Yt, formatFontFaceRule as Z, getColorSpaceSuffix as Zt, isFunctionsPolyfillEnabled as _, propHandlerRegistry as _t, getGlobalCounterStyles as a, hslToRgbValues as an, DEFAULT_NAME_PREFIX as at, resetConfig as b, DIMENSION_CHUNK_STYLES as bt, getGlobalInjector as c, makeCounterStyleName as ct, getGlobalStyles as d, validateNamePrefix as dt, resolveFunctionColor as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, hashString as ft, isConfigLocked as g, parsePropertyToken as gt, hasStylesGenerated as h, hasLocalProperties as ht, getGlobalConfigTokens as i, hexToRgb as in, PropertyTypeResolver as it, INNER_STYLES as j, registerLocalFunctionPolyfills as jt, DIMENSION_STYLES as k, parseFunctionName as kt, getGlobalKeyframes as l, makeKeyframeName as lt, hasGlobalRecipes as m, getEffectiveDefinition as mt, getConfig as n, getNamedColorHex as nn, styleHandlers as nt, getGlobalFontFaces as o, strToRgb as on, DEFAULT_ZERO_NAME_PREFIX as ot, hasGlobalKeyframes as p, extractLocalProperties as pt, formatCounterStyleRule as q, StyleParser as qt, getEffectiveProperties as r, getRgbValuesFromRgbaString as rn, createStyle as rt, getGlobalFunctions as s, normalizeDslName as sn, makeClassName as st, configure as t, Lru as tn, defineHandler as tt, getGlobalRecipes as u, tastyClassRegex as ut, isTestEnvironment as v, APPEARANCE_CHUNK_STYLES as vt, BLOCK_INNER_STYLES as w, POSITION_CHUNK_STYLES as wt, generateTypographyTokens as x, DISPLAY_CHUNK_STYLES as xt, markStylesGenerated as y, CHUNK_NAMES as yt, camelToKebab as z, parseStyle as zt };
12161
12505
 
12162
- //# sourceMappingURL=config-CCcE_tqx.js.map
12506
+ //# sourceMappingURL=config-CwQ-fAsp.js.map