@tenphi/tasty 0.0.0-snapshot.27b6708 → 0.0.0-snapshot.28b6339

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 (109) hide show
  1. package/README.md +38 -54
  2. package/dist/{babel-BUQGeOXA.d.ts → babel-Zs1Q03qt.d.ts} +2 -2
  3. package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/chunks/build-config-Mk7waOsw.js +45 -0
  5. package/dist/chunks/build-config-Mk7waOsw.js.map +1 -0
  6. package/dist/{collector-DTahQUiV.js → chunks/collector-Ducc5l3E.js} +29 -15
  7. package/dist/chunks/collector-Ducc5l3E.js.map +1 -0
  8. package/dist/chunks/config-engine-tKK7WxJO.js +331 -0
  9. package/dist/chunks/config-engine-tKK7WxJO.js.map +1 -0
  10. package/dist/chunks/css-definitions-Crstltrf.js +1229 -0
  11. package/dist/chunks/css-definitions-Crstltrf.js.map +1 -0
  12. package/dist/chunks/css-resources-Cyl_axbI.js +149 -0
  13. package/dist/chunks/css-resources-Cyl_axbI.js.map +1 -0
  14. package/dist/chunks/debug-HyyLIf6O.js +543 -0
  15. package/dist/chunks/debug-HyyLIf6O.js.map +1 -0
  16. package/dist/chunks/dsl-D7kBow--.js +1771 -0
  17. package/dist/chunks/dsl-D7kBow--.js.map +1 -0
  18. package/dist/chunks/hydration-CLdVKcH2.js +59 -0
  19. package/dist/chunks/hydration-CLdVKcH2.js.map +1 -0
  20. package/dist/chunks/react-runtime-UlZcbsEN.js +877 -0
  21. package/dist/chunks/react-runtime-UlZcbsEN.js.map +1 -0
  22. package/dist/chunks/runtime-engine-D2WOLvd6.js +3439 -0
  23. package/dist/chunks/runtime-engine-D2WOLvd6.js.map +1 -0
  24. package/dist/chunks/runtime-state-DOeH6rsG.js +138 -0
  25. package/dist/chunks/runtime-state-DOeH6rsG.js.map +1 -0
  26. package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-Bgl8FNGJ.js} +64 -24
  27. package/dist/chunks/shared-utils-Bgl8FNGJ.js.map +1 -0
  28. package/dist/chunks/style-engine-C4ZcOAPb.js +6915 -0
  29. package/dist/chunks/style-engine-C4ZcOAPb.js.map +1 -0
  30. package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-D4Rluo7u.js} +3 -3
  31. package/dist/chunks/zero-engine-D4Rluo7u.js.map +1 -0
  32. package/dist/{collector-DUaHCcTS.d.ts → collector-hl5KNH7k.d.ts} +23 -3
  33. package/dist/{config-YsxGv4tq.d.ts → config-DWL1ZYue.d.ts} +150 -55
  34. package/dist/core/index.d.ts +5 -5
  35. package/dist/core/index.js +9 -6
  36. package/dist/{index-PqN-DIpn.d.ts → index-D9N9GV24.d.ts} +129 -93
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +11 -922
  39. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-C_oMoJVd.d.ts} +2 -2
  40. package/dist/ssr/astro-client.js +1 -1
  41. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  42. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  43. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  44. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  45. package/dist/ssr/astro-middleware-extract.js +9 -0
  46. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  47. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  48. package/dist/ssr/astro-middleware.d.ts +3 -1
  49. package/dist/ssr/astro.d.ts +45 -3
  50. package/dist/ssr/astro.js +158 -15
  51. package/dist/ssr/astro.js.map +1 -1
  52. package/dist/ssr/index.d.ts +7 -7
  53. package/dist/ssr/index.js +4 -4
  54. package/dist/ssr/index.js.map +1 -1
  55. package/dist/ssr/next-config.d.ts +66 -0
  56. package/dist/ssr/next-config.js +135 -0
  57. package/dist/ssr/next-config.js.map +1 -0
  58. package/dist/ssr/next.d.ts +9 -2
  59. package/dist/ssr/next.js +27 -11
  60. package/dist/ssr/next.js.map +1 -1
  61. package/dist/static/index.d.ts +2 -2
  62. package/dist/static/index.js +1 -1
  63. package/dist/{index-Cd45t5NM.d.ts → types-jKZNpias.d.ts} +898 -803
  64. package/dist/zero/babel.d.ts +1 -1
  65. package/dist/zero/babel.js +25 -24
  66. package/dist/zero/babel.js.map +1 -1
  67. package/dist/zero/index.d.ts +1 -1
  68. package/dist/zero/index.js +1 -1
  69. package/dist/zero/next.d.ts +1 -1
  70. package/docs/README.md +5 -3
  71. package/docs/adoption.md +3 -3
  72. package/docs/ai-agents.md +83 -78
  73. package/docs/comparison.md +12 -12
  74. package/docs/configuration.md +47 -47
  75. package/docs/debug.md +19 -7
  76. package/docs/dsl.md +3 -3
  77. package/docs/getting-started.md +10 -10
  78. package/docs/injector.md +62 -25
  79. package/docs/methodology.md +3 -3
  80. package/docs/migration-v3.md +49 -49
  81. package/docs/plugins.md +37 -33
  82. package/docs/react-api.md +2 -2
  83. package/docs/runtime-benchmarks.md +351 -0
  84. package/docs/ssr.md +219 -61
  85. package/docs/styles.md +43 -11
  86. package/docs/tasty-static.md +136 -103
  87. package/package.json +80 -11
  88. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  89. package/dist/collector-DTahQUiV.js.map +0 -1
  90. package/dist/config-B5kHzuNz.js +0 -12269
  91. package/dist/config-B5kHzuNz.js.map +0 -1
  92. package/dist/context-CA8YKeMn.js +0 -24
  93. package/dist/context-CA8YKeMn.js.map +0 -1
  94. package/dist/core-Dr4u1NVD.js +0 -1566
  95. package/dist/core-Dr4u1NVD.js.map +0 -1
  96. package/dist/css-writer-B-J87ncv.js.map +0 -1
  97. package/dist/format-global-rules-DklyaXv-.js +0 -22
  98. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  99. package/dist/format-rules-DKOA-6qu.js +0 -130
  100. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  101. package/dist/hydrate-OeMX99We.js +0 -37
  102. package/dist/hydrate-OeMX99We.js.map +0 -1
  103. package/dist/index.js.map +0 -1
  104. package/dist/keyframes-CV8azJf3.js +0 -493
  105. package/dist/keyframes-CV8azJf3.js.map +0 -1
  106. package/dist/merge-styles-oklji0KB.js.map +0 -1
  107. package/dist/resolve-recipes-DTG81rzl.js +0 -144
  108. package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
  109. /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
@@ -0,0 +1,1771 @@
1
+ import { a as warnOnceDev, o as isDevEnv } from "./shared-utils-Bgl8FNGJ.js";
2
+ //#region src/parser/const.ts
3
+ const VALUE_KEYWORDS = new Set([
4
+ "auto",
5
+ "max-content",
6
+ "min-content",
7
+ "fit-content",
8
+ "stretch",
9
+ "initial",
10
+ "inherit",
11
+ "revert",
12
+ "unset",
13
+ "revert-layer"
14
+ ]);
15
+ const CSS_WIDE_KEYWORDS = new Set([
16
+ "initial",
17
+ "inherit",
18
+ "revert",
19
+ "unset",
20
+ "revert-layer"
21
+ ]);
22
+ /**
23
+ * Color functions that *derive* a color from other colors. They take no alpha
24
+ * channel, so opacity has to wrap the whole call in `color-mix()` instead of
25
+ * being appended after a slash.
26
+ */
27
+ const DERIVED_COLOR_FUNCS_LIST = [
28
+ "color-mix",
29
+ "color-contrast",
30
+ "contrast-color",
31
+ "light-dark"
32
+ ];
33
+ const DERIVED_COLOR_FUNCS = new Set(DERIVED_COLOR_FUNCS_LIST);
34
+ /**
35
+ * Every color function the parser recognizes. The ones outside
36
+ * `DERIVED_COLOR_FUNCS` take channels as arguments, optionally followed by
37
+ * `/ <alpha>` — which is where an opacity suffix writes.
38
+ */
39
+ const COLOR_FUNCS = new Set([
40
+ "rgb",
41
+ "rgba",
42
+ "hsl",
43
+ "hsla",
44
+ "hwb",
45
+ "lab",
46
+ "lch",
47
+ "oklab",
48
+ "oklch",
49
+ "color",
50
+ "device-cmyk",
51
+ "gray",
52
+ ...DERIVED_COLOR_FUNCS_LIST
53
+ ]);
54
+ /** A value that is a single `name(args)` call: captures the name and the args. */
55
+ const RE_FUNC_CALL = /^([a-z][a-z0-9-]*)\((.+)\)$/i;
56
+ const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
57
+ const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
58
+ const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
59
+ const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
60
+ const CANONICAL_FUNC_CASE = new Map([
61
+ ["translatex", "translateX"],
62
+ ["translatey", "translateY"],
63
+ ["translatez", "translateZ"],
64
+ ["scalex", "scaleX"],
65
+ ["scaley", "scaleY"],
66
+ ["scalez", "scaleZ"],
67
+ ["rotatex", "rotateX"],
68
+ ["rotatey", "rotateY"],
69
+ ["rotatez", "rotateZ"],
70
+ ["skewx", "skewX"],
71
+ ["skewy", "skewY"]
72
+ ]);
73
+ function canonicalFuncName(lowered) {
74
+ return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
75
+ }
76
+ //#endregion
77
+ //#region src/utils/string.ts
78
+ function toSnakeCase(str) {
79
+ return str.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`);
80
+ }
81
+ /**
82
+ * Normalize a DSL identifier to the CSS custom-property name it emits.
83
+ *
84
+ * CSS custom-property names are case-sensitive, so `$myVar` has to keep its
85
+ * inner case to reference `--myVar`. A leading capital is the one exception:
86
+ * `$Foo` is not a supported way to name a property, so the first character is
87
+ * folded rather than kebab-cased (`--foo`, not `---foo`) and every later
88
+ * character is left alone.
89
+ *
90
+ * Every place that turns a `$name` / `#name` identifier into a CSS name goes
91
+ * through this, so authoring a token and referencing it always agree.
92
+ */
93
+ function normalizeDslName(name) {
94
+ if (!name) return name;
95
+ const first = name[0];
96
+ const lower = first.toLowerCase();
97
+ return lower === first ? name : lower + name.slice(1);
98
+ }
99
+ /** A DSL identifier: `$name`, `$$name`, `#name` or `##name`. */
100
+ const RE_DSL_IDENTIFIER = /(\$\$?|##?)([a-zA-Z_][a-zA-Z0-9_-]*)/g;
101
+ /**
102
+ * Case-fold a DSL string for parsing, preserving the case of custom-property
103
+ * names.
104
+ *
105
+ * Everything the parser matches on — keywords, units, modifiers, function names,
106
+ * color-token names — is compared lowercase, so the whole string used to be
107
+ * lowercased up front. That also folded custom-property names, which are
108
+ * case-sensitive in CSS: `$myVar` referenced `var(--myvar)` while the token
109
+ * definition emitted `--myVar`, so the two could never meet.
110
+ *
111
+ * Identifier bodies now keep their case (their first character still folds, see
112
+ * {@link normalizeDslName}); everything else is lowercased exactly as before.
113
+ * Hex literals are excluded — `#FF0000` is a color, not a name, and must fold to
114
+ * match {@link RE_HEX}.
115
+ */
116
+ function foldDslCase(src) {
117
+ const folded = src.toLowerCase();
118
+ if (folded === src || !src.includes("$") && !src.includes("#")) return folded;
119
+ RE_DSL_IDENTIFIER.lastIndex = 0;
120
+ let out = "";
121
+ let last = 0;
122
+ let match;
123
+ while (match = RE_DSL_IDENTIFIER.exec(src)) {
124
+ const [whole, prefix, body] = match;
125
+ out += src.slice(last, match.index).toLowerCase();
126
+ out += prefix + (prefix[0] === "#" && RE_HEX.test(body.toLowerCase()) ? body.toLowerCase() : normalizeDslName(body));
127
+ last = match.index + whole.length;
128
+ }
129
+ return out + src.slice(last).toLowerCase();
130
+ }
131
+ //#endregion
132
+ //#region src/utils/color-math.ts
133
+ const OKLab_to_LMS_ab = [
134
+ [.3963377773761749, .2158037573099136],
135
+ [-.1055613458156586, -.0638541728258133],
136
+ [-.0894841775298119, -1.2914855480194092]
137
+ ];
138
+ const LMS_to_linear_sRGB_M = [
139
+ [
140
+ 4.076741636075959,
141
+ -3.307711539258062,
142
+ .2309699031821041
143
+ ],
144
+ [
145
+ -1.2684379732850313,
146
+ 2.6097573492876878,
147
+ -.3413193760026569
148
+ ],
149
+ [
150
+ -.004196076138675526,
151
+ -.703418617935936,
152
+ 1.7076146940746113
153
+ ]
154
+ ];
155
+ const OKLab_to_linear_sRGB_coefficients = [
156
+ [[-1.8817030993265873, -.8093650129914302], [
157
+ 1.19086277,
158
+ 1.76576728,
159
+ .59662641,
160
+ .75515197,
161
+ .56771245
162
+ ]],
163
+ [[1.8144407988010998, -1.194452667805235], [
164
+ .73956515,
165
+ -.45954404,
166
+ .08285427,
167
+ .1254107,
168
+ .14503204
169
+ ]],
170
+ [[.13110757611180954, 1.813339709266608], [
171
+ 1.35733652,
172
+ -.00915799,
173
+ -1.1513021,
174
+ -.50559606,
175
+ .00692167
176
+ ]]
177
+ ];
178
+ const dotXY = (vector, x, y) => vector[0] * x + vector[1] * y;
179
+ const dotXYZ = (matrix, x, y, z) => matrix[0] * x + matrix[1] * y + matrix[2] * z;
180
+ const INV_GAMMA = 1 / 2.4;
181
+ function srgbLinearToGamma(val) {
182
+ const sign = val < 0 ? -1 : 1;
183
+ const abs = Math.abs(val);
184
+ return abs > .0031308 ? sign * (1.055 * abs ** INV_GAMMA - .055) : 12.92 * val;
185
+ }
186
+ const TAU = 2 * Math.PI;
187
+ const K1 = .206;
188
+ const K2 = .03;
189
+ const K3 = 1.206 / 1.03;
190
+ const clamp$1 = (value, min, max) => Math.max(Math.min(value, max), min);
191
+ const constrainAngle = (angle) => (angle % 360 + 360) % 360;
192
+ const toe = (x) => .5 * (K3 * x - K1 + Math.sqrt((K3 * x - K1) * (K3 * x - K1) + 4 * K2 * K3 * x));
193
+ const toeInv = (x) => (x ** 2 + K1 * x) / (K3 * (x + K2));
194
+ const OKHST_REF_EPS = .05;
195
+ function yToL(y) {
196
+ return toe(Math.cbrt(Math.max(0, y)));
197
+ }
198
+ function yFromTone(t, eps = OKHST_REF_EPS) {
199
+ const den = Math.log(1 + eps) - Math.log(eps);
200
+ return Math.exp(t / 100 * den + Math.log(eps)) - eps;
201
+ }
202
+ function fromTone(t, eps = OKHST_REF_EPS) {
203
+ return yToL(yFromTone(t, eps));
204
+ }
205
+ const oklabToLinearSrgb = (lab) => {
206
+ const L = lab[0];
207
+ const a = lab[1];
208
+ const b = lab[2];
209
+ const l_ = L + .3963377773761749 * a + .2158037573099136 * b;
210
+ const m_ = L - .1055613458156586 * a - .0638541728258133 * b;
211
+ const s_ = L - .0894841775298119 * a - 1.2914855480194092 * b;
212
+ const l = l_ * l_ * l_;
213
+ const m = m_ * m_ * m_;
214
+ const s = s_ * s_ * s_;
215
+ return [
216
+ 4.076741636075959 * l - 3.307711539258062 * m + .2309699031821041 * s,
217
+ -1.2684379732850313 * l + 2.6097573492876878 * m - .3413193760026569 * s,
218
+ -.004196076138675526 * l - .703418617935936 * m + 1.7076146940746113 * s
219
+ ];
220
+ };
221
+ const computeMaxSaturationOKLC = (a, b) => {
222
+ const okCoeff = OKLab_to_linear_sRGB_coefficients;
223
+ const lmsToRgb = LMS_to_linear_sRGB_M;
224
+ let chnlCoeff;
225
+ let chnlLMS;
226
+ if (dotXY(okCoeff[0][0], a, b) > 1) {
227
+ chnlCoeff = okCoeff[0][1];
228
+ chnlLMS = lmsToRgb[0];
229
+ } else if (dotXY(okCoeff[1][0], a, b) > 1) {
230
+ chnlCoeff = okCoeff[1][1];
231
+ chnlLMS = lmsToRgb[1];
232
+ } else {
233
+ chnlCoeff = okCoeff[2][1];
234
+ chnlLMS = lmsToRgb[2];
235
+ }
236
+ const [k0, k1, k2, k3, k4] = chnlCoeff;
237
+ const [wl, wm, ws] = chnlLMS;
238
+ let sat = k0 + k1 * a + k2 * b + k3 * (a * a) + k4 * a * b;
239
+ const kl = dotXY(OKLab_to_LMS_ab[0], a, b);
240
+ const km = dotXY(OKLab_to_LMS_ab[1], a, b);
241
+ const ks = dotXY(OKLab_to_LMS_ab[2], a, b);
242
+ const l_ = 1 + sat * kl;
243
+ const m_ = 1 + sat * km;
244
+ const s_ = 1 + sat * ks;
245
+ const l = l_ ** 3;
246
+ const m = m_ ** 3;
247
+ const s = s_ ** 3;
248
+ const lds = 3 * kl * (l_ * l_);
249
+ const mds = 3 * km * (m_ * m_);
250
+ const sds = 3 * ks * (s_ * s_);
251
+ const lds2 = 6 * (kl * kl) * l_;
252
+ const mds2 = 6 * (km * km) * m_;
253
+ const sds2 = 6 * (ks * ks) * s_;
254
+ const f = wl * l + wm * m + ws * s;
255
+ const f1 = wl * lds + wm * mds + ws * sds;
256
+ const f2 = wl * lds2 + wm * mds2 + ws * sds2;
257
+ sat = sat - f * f1 / (f1 * f1 - .5 * f * f2);
258
+ return sat;
259
+ };
260
+ const findCuspOKLCH = (a, b) => {
261
+ const S_cusp = computeMaxSaturationOKLC(a, b);
262
+ const rgb_at_max = oklabToLinearSrgb([
263
+ 1,
264
+ S_cusp * a,
265
+ S_cusp * b
266
+ ]);
267
+ const L_cusp = Math.cbrt(1 / Math.max(Math.max(rgb_at_max[0], rgb_at_max[1]), Math.max(rgb_at_max[2], 0)));
268
+ return [L_cusp, L_cusp * S_cusp];
269
+ };
270
+ const findGamutIntersectionOKLCH = (a, b, l1, c1, l0, cusp) => {
271
+ const lmsToRgb = LMS_to_linear_sRGB_M;
272
+ let t;
273
+ if ((l1 - l0) * cusp[1] - (cusp[0] - l0) * c1 <= 0) {
274
+ const denom = c1 * cusp[0] + cusp[1] * (l0 - l1);
275
+ t = denom === 0 ? 0 : cusp[1] * l0 / denom;
276
+ } else {
277
+ const denom = c1 * (cusp[0] - 1) + cusp[1] * (l0 - l1);
278
+ t = denom === 0 ? 0 : cusp[1] * (l0 - 1) / denom;
279
+ const dl = l1 - l0;
280
+ const dc = c1;
281
+ const kl = dotXY(OKLab_to_LMS_ab[0], a, b);
282
+ const km = dotXY(OKLab_to_LMS_ab[1], a, b);
283
+ const ks = dotXY(OKLab_to_LMS_ab[2], a, b);
284
+ const ldt_ = dl + dc * kl;
285
+ const mdt_ = dl + dc * km;
286
+ const sdt_ = dl + dc * ks;
287
+ const L = l0 * (1 - t) + t * l1;
288
+ const C = t * c1;
289
+ const l_ = L + C * kl;
290
+ const m_ = L + C * km;
291
+ const s_ = L + C * ks;
292
+ const l = l_ ** 3;
293
+ const m = m_ ** 3;
294
+ const s = s_ ** 3;
295
+ const ldt = 3 * ldt_ * l_ * l_;
296
+ const mdt = 3 * mdt_ * m_ * m_;
297
+ const sdt = 3 * sdt_ * s_ * s_;
298
+ const ldt2 = 6 * ldt_ * ldt_ * l_;
299
+ const mdt2 = 6 * mdt_ * mdt_ * m_;
300
+ const sdt2 = 6 * sdt_ * sdt_ * s_;
301
+ let correction = Number.MAX_VALUE;
302
+ for (const matrix of lmsToRgb) {
303
+ const value = dotXYZ(matrix, l, m, s) - 1;
304
+ const slope = dotXYZ(matrix, ldt, mdt, sdt);
305
+ const slope2 = dotXYZ(matrix, ldt2, mdt2, sdt2);
306
+ const u = slope / (slope * slope - .5 * value * slope2);
307
+ if (u >= 0) correction = Math.min(correction, -value * u);
308
+ }
309
+ t += correction;
310
+ }
311
+ return t;
312
+ };
313
+ const computeSt = (cusp) => [cusp[1] / cusp[0], cusp[1] / (1 - cusp[0])];
314
+ const computeStMid = (a, b) => [.11516993 + 1 / (7.4477897 + 4.1590124 * b + a * (-2.19557347 + 1.75198401 * b + a * (-2.13704948 - 10.02301043 * b + a * (-4.24894561 + 5.38770819 * b + 4.69891013 * a)))), .11239642 + 1 / (1.6132032 - .68124379 * b + a * (.40370612 + .90148123 * b + a * (-.27087943 + .6122399 * b + a * (.00299215 - .45399568 * b - .14661872 * a))))];
315
+ const getCs = (L, a, b, cusp) => {
316
+ const cMax = findGamutIntersectionOKLCH(a, b, L, 1, L, cusp);
317
+ const stMax = computeSt(cusp);
318
+ const k = cMax / Math.min(L * stMax[0], (1 - L) * stMax[1]);
319
+ const stMid = computeStMid(a, b);
320
+ let ca = L * stMid[0];
321
+ let cb = (1 - L) * stMid[1];
322
+ const cMid = .9 * k * Math.sqrt(Math.sqrt(1 / (1 / ca ** 4 + 1 / cb ** 4)));
323
+ ca = L * .4;
324
+ cb = (1 - L) * .8;
325
+ return [
326
+ Math.sqrt(1 / (1 / ca ** 2 + 1 / cb ** 2)),
327
+ cMid,
328
+ cMax
329
+ ];
330
+ };
331
+ const okhslToOklab = (hsl) => {
332
+ let h = hsl[0];
333
+ const s = hsl[1];
334
+ const l = hsl[2];
335
+ const L = toeInv(l);
336
+ let a = 0;
337
+ let b = 0;
338
+ h = constrainAngle(h) / 360;
339
+ if (L !== 0 && L !== 1 && s !== 0) {
340
+ const a_ = Math.cos(TAU * h);
341
+ const b_ = Math.sin(TAU * h);
342
+ const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
343
+ const mid = .8;
344
+ const midInv = 1.25;
345
+ let t, k0, k1, k2;
346
+ if (s < mid) {
347
+ t = midInv * s;
348
+ k0 = 0;
349
+ k1 = mid * c0;
350
+ k2 = 1 - k1 / cMid;
351
+ } else {
352
+ t = 5 * (s - .8);
353
+ k0 = cMid;
354
+ k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
355
+ k2 = 1 - k1 / (cMax - cMid);
356
+ }
357
+ const c = k0 + t * k1 / (1 - k2 * t);
358
+ a = c * a_;
359
+ b = c * b_;
360
+ }
361
+ return [
362
+ L,
363
+ a,
364
+ b
365
+ ];
366
+ };
367
+ /**
368
+ * HSL to RGB.
369
+ * Algorithm from CSS Color 4 spec.
370
+ *
371
+ * @param h - Hue in degrees (0-360)
372
+ * @param s - Saturation (0-1)
373
+ * @param l - Lightness (0-1)
374
+ * @returns RGB values in 0-255 range (may have fractional values)
375
+ */
376
+ function hslToRgbValues(h, s, l) {
377
+ const a = s * Math.min(l, 1 - l);
378
+ const f = (n) => {
379
+ const k = (n + h / 30) % 12;
380
+ return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
381
+ };
382
+ return [
383
+ f(0) * 255,
384
+ f(8) * 255,
385
+ f(4) * 255
386
+ ];
387
+ }
388
+ /**
389
+ * OKHSL to sRGB (0-1 range).
390
+ * @param h - Hue in degrees (0-360)
391
+ * @param s - Saturation (0-1)
392
+ * @param l - Lightness (0-1)
393
+ * @returns sRGB values in 0-1 range, clamped to gamut
394
+ */
395
+ function okhslToSrgb(h, s, l) {
396
+ const linearRGB = oklabToLinearSrgb(okhslToOklab([
397
+ h,
398
+ s,
399
+ l
400
+ ]));
401
+ return [
402
+ clamp$1(srgbLinearToGamma(linearRGB[0]), 0, 1),
403
+ clamp$1(srgbLinearToGamma(linearRGB[1]), 0, 1),
404
+ clamp$1(srgbLinearToGamma(linearRGB[2]), 0, 1)
405
+ ];
406
+ }
407
+ /**
408
+ * OKHST to sRGB (0-1 range).
409
+ * @param h - Hue in degrees (0-360)
410
+ * @param s - Saturation (0-1)
411
+ * @param t - Tone (0-1)
412
+ * @returns sRGB values in 0-1 range, clamped to gamut
413
+ */
414
+ function okhstToSrgb(h, s, t) {
415
+ return okhslToSrgb(h, clamp$1(s, 0, 1), clamp$1(fromTone(t * 100), 0, 1));
416
+ }
417
+ /**
418
+ * OKLCH to sRGB (0-255 range).
419
+ * @param L - Lightness (0-1)
420
+ * @param C - Chroma (typically 0-0.4)
421
+ * @param H - Hue in degrees (0-360)
422
+ * @returns RGB values in 0-255 range, clamped to gamut
423
+ */
424
+ function oklchToRgbValues(L, C, H) {
425
+ const hRad = H * Math.PI / 180;
426
+ const linear = oklabToLinearSrgb([
427
+ L,
428
+ C * Math.cos(hRad),
429
+ C * Math.sin(hRad)
430
+ ]);
431
+ return [
432
+ clamp$1(srgbLinearToGamma(linear[0]), 0, 1) * 255,
433
+ clamp$1(srgbLinearToGamma(linear[1]), 0, 1) * 255,
434
+ clamp$1(srgbLinearToGamma(linear[2]), 0, 1) * 255
435
+ ];
436
+ }
437
+ let _namedColorHex = null;
438
+ function getNamedColorHex() {
439
+ if (_namedColorHex) return _namedColorHex;
440
+ const names = "aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen".split(" ");
441
+ const hex = "f0f8fffaebd700ffff7fffd4f0fffff5f5dcffe4c4000000ffebcd0000ff8a2be2a52a2adeb8875f9ea07fff00d2691eff7f506495edfff8dcdc143c00ffff00008b008b8bb8860ba9a9a9006400a9a9a9bdb76b8b008b556b2fff8c009932cc8b0000e9967a8fbc8f483d8b2f4f4f2f4f4f00ced19400d3ff149300bfff6969696969691e90ffb22222fffaf0228b22ff00ffdcdcdcf8f8ffffd700daa520808080008000adff2f808080f0fff0ff69b4cd5c5c4b0082fffff0f0e68ce6e6fafff0f57cfc00fffacdadd8e6f08080e0fffffafad2d3d3d390ee90d3d3d3ffb6c1ffa07a20b2aa87cefa778899778899b0c4deffffe000ff0032cd32faf0e6ff00ff80000066cdaa0000cdba55d39370db3cb3717b68ee00fa9a48d1ccc71585191970f5fffaffe4e1ffe4b5ffdead000080fdf5e68080006b8e23ffa500ff4500da70d6eee8aa98fb98afeeeedb7093ffefd5ffdab9cd853fffc0cbdda0ddb0e0e6800080663399ff0000bc8f8f4169e18b4513fa8072f4a4602e8b57fff5eea0522dc0c0c087ceeb6a5acd708090708090fffafa00ff7f4682b4d2b48c008080d8bfd8ff634740e0d0ee82eef5deb3fffffff5f5f5ffff009acd32";
442
+ const colors = /* @__PURE__ */ new Map();
443
+ for (let i = 0; i < names.length; i++) colors.set(names[i], `#${hex.slice(i * 6, i * 6 + 6)}`);
444
+ return _namedColorHex = colors;
445
+ }
446
+ /**
447
+ * Convert hex color string to `rgb()` CSS string.
448
+ * Supports 3, 4, 6, and 8 character hex values (with or without `#`).
449
+ */
450
+ function hexToRgb(hex) {
451
+ const matched = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (_m, r, g, b) => "#" + r + r + g + g + b + b).substring(1).match(/.{2}/g);
452
+ if (!matched) return null;
453
+ const rgba = matched.map((x, i) => parseInt(x, 16) * (i === 3 ? 1 / 255 : 1));
454
+ if (rgba.some((v) => Number.isNaN(v))) return null;
455
+ if (rgba.length >= 3) return `rgb(${rgba.slice(0, 3).join(" ")}${rgba.length > 3 ? ` / ${rgba[3]}` : ""})`;
456
+ return null;
457
+ }
458
+ /**
459
+ * Extract RGB values from an `rgb()`/`rgba()` string.
460
+ * Supports comma-separated, space-separated, fractional, percentage,
461
+ * and slash alpha notation.
462
+ *
463
+ * @returns Array of RGB values (0-255 range), converting percentages as needed.
464
+ */
465
+ function getRgbValuesFromRgbaString(str) {
466
+ const match = str.match(/rgba?\(([^)]+)\)/i);
467
+ if (!match) return [];
468
+ const [colorPart] = match[1].trim().split("/");
469
+ return colorPart.trim().split(/[,\s]+/).filter(Boolean).slice(0, 3).map((part) => {
470
+ part = part.trim();
471
+ if (part.endsWith("%")) return parseFloat(part) / 100 * 255;
472
+ return parseFloat(part);
473
+ });
474
+ }
475
+ /**
476
+ * Convert any recognized color string to an `rgb()` CSS string.
477
+ * Handles hex, `okhsl()`, `okhst()`, `hsl()`/`hsla()`, named CSS colors,
478
+ * and `rgb()`/`rgba()` pass-through.
479
+ */
480
+ function strToRgb(color, _ignoreAlpha = false) {
481
+ if (!color) return void 0;
482
+ if (color.startsWith("rgb")) return color;
483
+ if (color.startsWith("#")) return hexToRgb(color);
484
+ if (color.startsWith("oklch(")) return oklchStringToRgb(color);
485
+ if (color.startsWith("hsl")) return hslStringToRgb(color);
486
+ const namedHex = getNamedColorHex().get(color.toLowerCase());
487
+ if (namedHex) return hexToRgb(namedHex);
488
+ return null;
489
+ }
490
+ /**
491
+ * Convert an HSL/HSLA color string to an `rgb()`/`rgba()` CSS string.
492
+ * Supports modern space-separated and legacy comma-separated syntax,
493
+ * deg/turn/rad hue units, and slash alpha notation.
494
+ */
495
+ function hslStringToRgb(hslStr) {
496
+ const match = hslStr.match(/hsla?\(([^)]+)\)/i);
497
+ if (!match) return null;
498
+ const [colorPart, slashAlpha] = match[1].trim().split("/");
499
+ const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
500
+ if (parts.length < 3) return null;
501
+ const alphaPart = slashAlpha?.trim() || (parts.length >= 4 ? parts[3] : null);
502
+ let h = parseFloat(parts[0]);
503
+ const hueStr = parts[0].toLowerCase();
504
+ if (hueStr.endsWith("turn")) h = parseFloat(hueStr) * 360;
505
+ else if (hueStr.endsWith("rad")) h = parseFloat(hueStr) * 180 / Math.PI;
506
+ h = (h % 360 + 360) % 360;
507
+ const parsePercent = (val) => {
508
+ const num = parseFloat(val);
509
+ return val.includes("%") ? num / 100 : num;
510
+ };
511
+ const s = Math.max(0, Math.min(1, parsePercent(parts[1])));
512
+ const l = Math.max(0, Math.min(1, parsePercent(parts[2])));
513
+ const [r, g, b] = hslToRgbValues(h, s, l);
514
+ if (alphaPart) {
515
+ const alpha = parseFloat(alphaPart.trim());
516
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;
517
+ }
518
+ return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
519
+ }
520
+ /**
521
+ * Convert an `oklch()` color string to an `rgb()`/`rgba()` CSS string.
522
+ * Supports deg/turn/rad hue units and percentage lightness.
523
+ */
524
+ function oklchStringToRgb(oklchStr) {
525
+ const match = oklchStr.match(/oklch\(([^)]+)\)/i);
526
+ if (!match) return null;
527
+ const [colorPart, alphaPart] = match[1].trim().split("/");
528
+ const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
529
+ if (parts.length < 3) return null;
530
+ const parsePercent = (val) => {
531
+ const num = parseFloat(val);
532
+ return val.includes("%") ? num / 100 : num;
533
+ };
534
+ const L = Math.max(0, Math.min(1, parsePercent(parts[0])));
535
+ const C = Math.max(0, parseFloat(parts[1]));
536
+ let H = parseFloat(parts[2]);
537
+ const hueStr = parts[2].toLowerCase();
538
+ if (hueStr.endsWith("turn")) H = parseFloat(hueStr) * 360;
539
+ else if (hueStr.endsWith("rad")) H = parseFloat(hueStr) * 180 / Math.PI;
540
+ else if (hueStr.endsWith("deg")) H = parseFloat(hueStr);
541
+ const [r, g, b] = oklchToRgbValues(L, C, H);
542
+ if (alphaPart) {
543
+ const alpha = parseFloat(alphaPart.trim());
544
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;
545
+ }
546
+ return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
547
+ }
548
+ //#endregion
549
+ //#region src/utils/color-space.ts
550
+ /**
551
+ * Set the alpha of a whole color, replacing any it already carries.
552
+ *
553
+ * CSS relative color syntax is how opacity is applied to every color Tasty
554
+ * emits. `oklch(from <color> l c h / <alpha>)` copies the channels over and
555
+ * writes the alpha slot, which needs nothing from the color except that it *is*
556
+ * a color: a `color-mix()`, a `light-dark()`, a `currentcolor`, a
557
+ * `--name-color` written by hand-authored CSS that Tasty never defined — all of
558
+ * them work, where writing into a channel slot directly would require channels
559
+ * the engine has no way to compute.
560
+ *
561
+ * Alpha is *replaced*, not composed. A token holding `rgb(255 0 0 / .8)` faded
562
+ * to `.5` is alpha `.5`. `color-mix()` against `transparent` cannot do this —
563
+ * it would multiply the two to `.4`.
564
+ *
565
+ * The alpha slot takes a `<number>` or a `<percentage>`, so an opacity custom
566
+ * property passes straight through in whichever form the author declared it.
567
+ *
568
+ * The space is always `oklch`, regardless of the configured {@link ColorSpace}:
569
+ * it is unbounded, so a wide-gamut color survives the round trip that a
570
+ * gamut-limited space would clamp, and it is the space the computed value used
571
+ * to be reported in. Channels are copied rather than interpolated, so the polar
572
+ * form costs nothing even for an achromatic color, whose hue is carried through
573
+ * untouched.
574
+ */
575
+ function overrideColorAlpha(color, alpha) {
576
+ return `oklch(from ${color} l c h / ${alpha})`;
577
+ }
578
+ /**
579
+ * Compose an alpha onto a color, multiplying with any it already carries.
580
+ *
581
+ * This is the counterpart to {@link overrideColorAlpha}, and the difference is
582
+ * the point. A design token names a color, so fading it *sets* its alpha. But
583
+ * `currentcolor` is the color an element **inherits**, which an ancestor may
584
+ * already have faded — `#current.4` there means "40% of what reaches me", and a
585
+ * nested `#current.18` under it composes to `.072`. Design systems build ramps
586
+ * out of exactly that, so `#current` composes and a token replaces.
587
+ *
588
+ * `color-mix()` against `transparent` is what composes: mixing premultiplied
589
+ * leaves the channels alone and multiplies the alphas. Its percentage slot takes
590
+ * no `<number>`, so a `$prop` alpha has to be scaled — which is why an opacity
591
+ * property used as `#current.$prop` has to hold a unitless number, where a token
592
+ * accepts either form.
593
+ */
594
+ function mixColorAlpha(color, percentage) {
595
+ return `color-mix(in oklab, ${color} ${percentage}, transparent)`;
596
+ }
597
+ /**
598
+ * Matches what {@link overrideColorAlpha} builds. The first group is greedy so a
599
+ * nested override splits on its outermost layer.
600
+ */
601
+ const RE_ALPHA_OVERRIDE = /^oklch\(from (.+) l c h \/ (.+)\)$/;
602
+ /**
603
+ * Split what {@link overrideColorAlpha} builds back into the color it faded and
604
+ * the alpha, or `null` when the value is not one of those.
605
+ */
606
+ function parseAlphaOverride(value) {
607
+ const match = value.match(RE_ALPHA_OVERRIDE);
608
+ return match ? {
609
+ color: match[1],
610
+ alpha: match[2]
611
+ } : null;
612
+ }
613
+ //#endregion
614
+ //#region src/parser/types.ts
615
+ const makeEmptyPart = () => ({
616
+ mods: [],
617
+ values: [],
618
+ colors: [],
619
+ all: [],
620
+ output: ""
621
+ });
622
+ const makeEmptyDetails = () => ({
623
+ input: "",
624
+ output: "",
625
+ mods: [],
626
+ values: [],
627
+ colors: [],
628
+ all: [],
629
+ parts: []
630
+ });
631
+ //#endregion
632
+ //#region src/parser/classify.ts
633
+ /**
634
+ * Convert an opacity suffix to the alpha value it denotes.
635
+ *
636
+ * The authored digits are kept verbatim — `.07` stays `.07` rather than being
637
+ * multiplied into `7.000000000000001%` — and a `$prop` suffix passes the
638
+ * reference straight through, so it works whether the property holds a
639
+ * `<number>` or a `<percentage>`. Both are what the alpha slot accepts.
640
+ */
641
+ function alphaSuffixToAlpha(rawAlpha) {
642
+ if (rawAlpha.startsWith("$")) return `var(--${rawAlpha.slice(1)})`;
643
+ if (rawAlpha === "0") return "0";
644
+ return `.${rawAlpha}`;
645
+ }
646
+ /** Apply an opacity suffix to a color, replacing any alpha it already carries. */
647
+ function fadeColor(color, rawAlpha) {
648
+ return overrideColorAlpha(color, alphaSuffixToAlpha(rawAlpha));
649
+ }
650
+ /**
651
+ * Convert an opacity suffix to the percentage `color-mix()` needs, by shifting
652
+ * the decimal point rather than multiplying: `.07` is `7%`, not the
653
+ * `7.000000000000001%` that `parseFloat('.07') * 100` produces.
654
+ */
655
+ function alphaSuffixToPercentage(rawAlpha) {
656
+ if (rawAlpha.startsWith("$")) return `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
657
+ if (rawAlpha === "0") return "0%";
658
+ const digits = rawAlpha.length === 1 ? `${rawAlpha}0` : rawAlpha;
659
+ const whole = String(Number(digits.slice(0, 2)));
660
+ const fraction = digits.slice(2);
661
+ return `${fraction ? `${whole}.${fraction}` : whole}%`;
662
+ }
663
+ /**
664
+ * Apply an opacity suffix to `currentcolor`, composing with any alpha an
665
+ * ancestor already applied. See {@link mixColorAlpha} for why this differs from
666
+ * how a token is faded.
667
+ */
668
+ function fadeCurrentColor(rawAlpha) {
669
+ return mixColorAlpha("currentcolor", alphaSuffixToPercentage(rawAlpha));
670
+ }
671
+ /**
672
+ * Whether parsed function arguments hold a color. Colors reach either the color
673
+ * bucket (`#token`, a nested color function, `transparent`) or — for the CSS
674
+ * named colors, which the parser has no token syntax for — the modifier bucket.
675
+ */
676
+ function hasColorArgs(parsed) {
677
+ const namedColors = getNamedColorHex();
678
+ return parsed.groups.some((group) => group.colors.length > 0 || group.mods.some((mod) => namedColors.has(mod)));
679
+ }
680
+ /**
681
+ * Re-parses a value through the parser until it stabilizes (no changes)
682
+ * or max iterations reached. This allows units to reference other units.
683
+ * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'
684
+ */
685
+ function resolveUntilStable(value, opts, recurse, maxIterations = 10) {
686
+ let current = value;
687
+ for (let i = 0; i < maxIterations; i++) {
688
+ const unitMatch = current.match(RE_UNIT_NUM);
689
+ if (!unitMatch) break;
690
+ const unitName = unitMatch[1];
691
+ if (!opts.units || !(unitName in opts.units)) break;
692
+ const result = recurse(current);
693
+ if (result.output === current) break;
694
+ current = result.output;
695
+ }
696
+ return current;
697
+ }
698
+ function classify(raw, opts, recurse) {
699
+ const token = raw.trim();
700
+ if (!token) return {
701
+ bucket: 2,
702
+ processed: ""
703
+ };
704
+ if (token.includes("(")) {
705
+ let depth = 0;
706
+ let inQuote = 0;
707
+ for (let i = 0; i < token.length; i++) {
708
+ const ch = token[i];
709
+ if (inQuote) {
710
+ if (ch === inQuote && token[i - 1] !== "\\") inQuote = 0;
711
+ continue;
712
+ }
713
+ if (ch === "\"" || ch === "'") {
714
+ inQuote = ch;
715
+ continue;
716
+ }
717
+ if (ch === "(") depth++;
718
+ else if (ch === ")" && depth) depth--;
719
+ }
720
+ if (depth !== 0) {
721
+ console.warn("[Tasty] skipped invalid function token with unmatched parentheses:", token);
722
+ return {
723
+ bucket: 2,
724
+ processed: ""
725
+ };
726
+ }
727
+ }
728
+ if (token.startsWith("\"") && token.endsWith("\"") || token.startsWith("'") && token.endsWith("'")) return {
729
+ bucket: 1,
730
+ processed: token
731
+ };
732
+ if (token.startsWith("$$")) {
733
+ const rest = token.slice(2);
734
+ const openIdx = rest.indexOf("(");
735
+ if (openIdx > 0 && rest.endsWith(")")) {
736
+ const fname = rest.slice(0, openIdx);
737
+ if (/^[a-z_][a-z0-9-_]*$/i.test(fname)) {
738
+ const inner = rest.slice(openIdx + 1, -1).trim();
739
+ const cssName = `--${fname}`;
740
+ if (opts.functions && cssName in opts.functions) {
741
+ const groups = inner ? new StyleParser(opts).process(inner).groups : [];
742
+ const funcResult = opts.functions[cssName](groups);
743
+ if (funcResult !== "") return classify(funcResult, {
744
+ ...opts,
745
+ functions: void 0
746
+ }, recurse);
747
+ }
748
+ return {
749
+ bucket: 1,
750
+ processed: `--${fname}(${inner ? recurse(inner).output : ""})`
751
+ };
752
+ }
753
+ }
754
+ if (/^[a-z_][a-z0-9-_]*$/i.test(rest)) return {
755
+ bucket: 1,
756
+ processed: `--${rest}`
757
+ };
758
+ }
759
+ if (token.startsWith("##")) {
760
+ const rest = token.slice(2);
761
+ const openIdx = rest.indexOf("(");
762
+ if (openIdx > 0 && rest.endsWith(")")) {
763
+ const fname = rest.slice(0, openIdx);
764
+ if (/^[a-z_][a-z0-9-_]*$/i.test(fname)) {
765
+ const inner = rest.slice(openIdx + 1, -1).trim();
766
+ return {
767
+ bucket: 1,
768
+ processed: `--${fname}-color(${inner ? recurse(inner).output : ""})`
769
+ };
770
+ }
771
+ }
772
+ if (/^[a-z_][a-z0-9-_]*$/i.test(rest)) return {
773
+ bucket: 1,
774
+ processed: `--${rest}-color`
775
+ };
776
+ }
777
+ if (token === "#current") return {
778
+ bucket: 0,
779
+ processed: "currentcolor"
780
+ };
781
+ const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
782
+ if (currentAlphaMatch) return {
783
+ bucket: 0,
784
+ processed: fadeCurrentColor(currentAlphaMatch[1])
785
+ };
786
+ if (token[0] === "$" || token[0] === "#") {
787
+ const predefinedTokens = getGlobalPredefinedTokens();
788
+ if (predefinedTokens) {
789
+ const lookupKey = token.toLowerCase();
790
+ if (lookupKey in predefinedTokens) {
791
+ const tokenValue = predefinedTokens[lookupKey];
792
+ return classify(foldDslCase(tokenValue), opts, recurse);
793
+ }
794
+ if (token[0] === "#") {
795
+ const alphaMatch = token.match(/^(#[a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
796
+ if (alphaMatch) {
797
+ const [, baseToken, rawAlpha] = alphaMatch;
798
+ const baseKey = baseToken.toLowerCase();
799
+ if (baseKey in predefinedTokens) {
800
+ const resolvedValue = predefinedTokens[baseKey];
801
+ if (resolvedValue.startsWith("#")) return classify(`${foldDslCase(resolvedValue)}.${rawAlpha}`, opts, recurse);
802
+ const funcMatch = resolvedValue.match(RE_FUNC_CALL);
803
+ if (funcMatch) {
804
+ const [, funcName, args] = funcMatch;
805
+ const lowerFunc = funcName.toLowerCase();
806
+ if (DERIVED_COLOR_FUNCS.has(lowerFunc)) return {
807
+ bucket: 0,
808
+ processed: fadeColor(classify(foldDslCase(resolvedValue), opts, recurse).processed, rawAlpha)
809
+ };
810
+ const isCustomFunc = !!(opts.functions && lowerFunc in opts.functions && !COLOR_FUNCS.has(lowerFunc) && !COLOR_FUNCS.has(funcName.replace(/a$/i, "").toLowerCase()));
811
+ const normalizedFunc = isCustomFunc ? lowerFunc : funcName.replace(/a$/i, "").toLowerCase();
812
+ if (!(COLOR_FUNCS.has(normalizedFunc) || COLOR_FUNCS.has(lowerFunc) || isCustomFunc)) return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
813
+ let alpha;
814
+ if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
815
+ else alpha = rawAlpha === "0" ? "0" : `.${rawAlpha}`;
816
+ const normalizeArgs = (a) => {
817
+ let result = "";
818
+ let depth = 0;
819
+ for (let i = 0; i < a.length; i++) {
820
+ const c = a[i];
821
+ if (c === "(") {
822
+ depth++;
823
+ result += c;
824
+ } else if (c === ")") {
825
+ depth = Math.max(0, depth - 1);
826
+ result += c;
827
+ } else if (c === "," && depth === 0) {
828
+ while (i + 1 < a.length && /\s/.test(a[i + 1])) i++;
829
+ result += " ";
830
+ } else result += c;
831
+ }
832
+ return result;
833
+ };
834
+ const findLastTopLevel = (str, ch) => {
835
+ let depth = 0;
836
+ for (let i = str.length - 1; i >= 0; i--) {
837
+ const c = str[i];
838
+ if (c === ")") depth++;
839
+ else if (c === "(") depth = Math.max(0, depth - 1);
840
+ else if (c === ch && depth === 0) return i;
841
+ }
842
+ return -1;
843
+ };
844
+ const slashIdx = findLastTopLevel(args, "/");
845
+ const hasModernAlpha = slashIdx !== -1;
846
+ let topLevelCommaCount = 0;
847
+ let lastTopLevelComma = -1;
848
+ {
849
+ let depth = 0;
850
+ for (let i = 0; i < args.length; i++) {
851
+ const c = args[i];
852
+ if (c === "(") depth++;
853
+ else if (c === ")") depth = Math.max(0, depth - 1);
854
+ else if (c === "," && depth === 0) {
855
+ topLevelCommaCount++;
856
+ lastTopLevelComma = i;
857
+ }
858
+ }
859
+ }
860
+ const hasLegacyAlpha = !hasModernAlpha && /a$/i.test(funcName) && topLevelCommaCount === 3;
861
+ const constructed = `${normalizedFunc}(${hasModernAlpha || hasLegacyAlpha ? normalizeArgs(hasModernAlpha ? args.slice(0, slashIdx).trim() : args.slice(0, lastTopLevelComma).trim()) : normalizeArgs(args)} / ${alpha})`;
862
+ if (!COLOR_FUNCS.has(normalizedFunc) && opts.functions && normalizedFunc in opts.functions) return classify(constructed, opts, recurse);
863
+ return {
864
+ bucket: 0,
865
+ processed: constructed
866
+ };
867
+ }
868
+ return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
869
+ }
870
+ }
871
+ }
872
+ }
873
+ }
874
+ if (token.match(/^var\(--([a-zA-Z0-9-]+)-color\)$/)) return {
875
+ bucket: 0,
876
+ processed: token
877
+ };
878
+ if (token.startsWith("url(")) return {
879
+ bucket: 1,
880
+ processed: token
881
+ };
882
+ if (token[0] === "$") {
883
+ const identMatch = token.match(/^\$([a-z_][a-zA-Z0-9-_]*)$/);
884
+ if (identMatch) {
885
+ const name = identMatch[1];
886
+ const processed = `var(--${name})`;
887
+ return {
888
+ bucket: name.endsWith("-color") ? 3 : 1,
889
+ processed
890
+ };
891
+ }
892
+ }
893
+ if (token[0] === "#" && token.length > 1) {
894
+ const alphaMatch = token.match(/^#([a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
895
+ if (alphaMatch) {
896
+ const [, base, rawAlpha] = alphaMatch;
897
+ return {
898
+ bucket: 0,
899
+ processed: fadeColor(`var(--${base}-color)`, rawAlpha)
900
+ };
901
+ }
902
+ const name = token.slice(1);
903
+ if (RE_HEX.test(name)) return {
904
+ bucket: 0,
905
+ processed: `var(--${name}-color, #${name})`
906
+ };
907
+ return {
908
+ bucket: 0,
909
+ processed: `var(--${name}-color)`
910
+ };
911
+ }
912
+ const openIdx = token.indexOf("(");
913
+ if (openIdx > 0 && token.endsWith(")")) {
914
+ const fname = token.slice(0, openIdx);
915
+ const inner = token.slice(openIdx + 1, -1);
916
+ if (COLOR_FUNCS.has(fname)) {
917
+ const parsedInner = recurse(inner);
918
+ const argProcessed = parsedInner.output.replace(/,\s+/g, ",");
919
+ const processed = `${canonicalFuncName(fname)}(${argProcessed})`;
920
+ return {
921
+ bucket: fname === "light-dark" && !hasColorArgs(parsedInner) ? 1 : 0,
922
+ processed
923
+ };
924
+ }
925
+ if (opts.functions && fname in opts.functions) {
926
+ const tmp = new StyleParser(opts).process(inner);
927
+ return classify(opts.functions[fname](tmp.groups), {
928
+ ...opts,
929
+ functions: void 0
930
+ }, recurse);
931
+ }
932
+ const argProcessed = recurse(inner).output;
933
+ return {
934
+ bucket: 1,
935
+ processed: `${canonicalFuncName(fname)}(${argProcessed})`
936
+ };
937
+ }
938
+ if (token.startsWith("(") && token.endsWith(")")) {
939
+ const colorMatch = token.slice(1, -1).match(/^#([a-z0-9-]+)\s*,\s*(.*)$/i);
940
+ if (colorMatch) {
941
+ const [, name, fallback] = colorMatch;
942
+ return {
943
+ bucket: 0,
944
+ processed: `var(--${name}-color, ${recurse(fallback).output})`
945
+ };
946
+ }
947
+ }
948
+ if (token.startsWith("(") && token.endsWith(")")) {
949
+ const match = token.slice(1, -1).match(/^\$([a-z_][a-zA-Z0-9-_]*)\s*,\s*(.*)$/);
950
+ if (match) {
951
+ const [, name, fallback] = match;
952
+ const processedFallback = recurse(fallback).output;
953
+ return {
954
+ bucket: name.endsWith("-color") ? 3 : 1,
955
+ processed: `var(--${name}, ${processedFallback})`
956
+ };
957
+ }
958
+ }
959
+ if (token[0] === "(" && token[token.length - 1] === ")") return {
960
+ bucket: 1,
961
+ processed: `calc(${recurse(token.slice(1, -1)).output})`
962
+ };
963
+ const um = token.match(RE_UNIT_NUM);
964
+ if (um) {
965
+ const unit = um[1];
966
+ const numericPart = parseFloat(token.slice(0, -unit.length));
967
+ const handler = opts.units && opts.units[unit];
968
+ if (handler) if (typeof handler === "string") {
969
+ const rawMatch = handler.match(RE_RAW_UNIT);
970
+ if (rawMatch) {
971
+ const [, baseNum, cssUnit] = rawMatch;
972
+ return {
973
+ bucket: 1,
974
+ processed: resolveUntilStable(`${numericPart * parseFloat(baseNum)}${cssUnit}`, opts, recurse)
975
+ };
976
+ }
977
+ const base = handler;
978
+ if (numericPart === 1) return {
979
+ bucket: 1,
980
+ processed: base
981
+ };
982
+ return {
983
+ bucket: 1,
984
+ processed: `calc(${numericPart} * ${base})`
985
+ };
986
+ } else return {
987
+ bucket: 1,
988
+ processed: handler(numericPart)
989
+ };
990
+ }
991
+ if (/^[+-]?(?:\d*\.\d+|\d+)[a-z%]+$/.test(token)) return {
992
+ bucket: 1,
993
+ processed: token
994
+ };
995
+ if (RE_NUMBER.test(token)) return {
996
+ bucket: 1,
997
+ processed: token
998
+ };
999
+ if (VALUE_KEYWORDS.has(token)) return {
1000
+ bucket: 1,
1001
+ processed: token
1002
+ };
1003
+ if (token === "transparent" || token === "currentcolor") return {
1004
+ bucket: 0,
1005
+ processed: token
1006
+ };
1007
+ return {
1008
+ bucket: 2,
1009
+ processed: token
1010
+ };
1011
+ }
1012
+ //#endregion
1013
+ //#region src/parser/lru.ts
1014
+ /**
1015
+ * Doubly-linked-list LRU.
1016
+ *
1017
+ * The list holds direct node references rather than keys, so rewiring on a
1018
+ * `get()` of a non-MRU entry is pure pointer work. Storing keys instead cost
1019
+ * three extra `Map.get()` lookups per read (prev, next and the current head),
1020
+ * which is measurable on these read-mostly caches: they are hit on every
1021
+ * render once the style pipeline is warm.
1022
+ */
1023
+ var Lru = class {
1024
+ limit;
1025
+ map = /* @__PURE__ */ new Map();
1026
+ head = null;
1027
+ tail = null;
1028
+ onEvict;
1029
+ constructor(limit = 1e3, onEvict) {
1030
+ this.limit = limit;
1031
+ let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
1032
+ if (normalized <= 0) normalized = 1e3;
1033
+ this.limit = normalized;
1034
+ this.onEvict = onEvict;
1035
+ }
1036
+ setOnEvict(fn) {
1037
+ this.onEvict = fn;
1038
+ }
1039
+ get(key) {
1040
+ const node = this.map.get(key);
1041
+ if (node === void 0) return void 0;
1042
+ if (this.head !== node) this.moveToHead(node);
1043
+ return node.value;
1044
+ }
1045
+ set(key, value) {
1046
+ const existing = this.map.get(key);
1047
+ if (existing !== void 0) {
1048
+ existing.value = value;
1049
+ if (this.head !== existing) this.moveToHead(existing);
1050
+ return;
1051
+ }
1052
+ const node = {
1053
+ key,
1054
+ prev: null,
1055
+ next: this.head,
1056
+ value
1057
+ };
1058
+ if (this.head) this.head.prev = node;
1059
+ this.head = node;
1060
+ if (!this.tail) this.tail = node;
1061
+ this.map.set(key, node);
1062
+ if (this.map.size > this.limit) this.evict();
1063
+ }
1064
+ delete(key) {
1065
+ const node = this.map.get(key);
1066
+ if (node === void 0) return;
1067
+ this.detach(node);
1068
+ this.map.delete(key);
1069
+ }
1070
+ keys() {
1071
+ return this.map.keys();
1072
+ }
1073
+ detach(node) {
1074
+ if (node.prev) node.prev.next = node.next;
1075
+ if (node.next) node.next.prev = node.prev;
1076
+ if (this.head === node) this.head = node.next;
1077
+ if (this.tail === node) this.tail = node.prev;
1078
+ node.prev = null;
1079
+ node.next = null;
1080
+ }
1081
+ moveToHead(node) {
1082
+ this.detach(node);
1083
+ node.next = this.head;
1084
+ if (this.head) this.head.prev = node;
1085
+ this.head = node;
1086
+ if (!this.tail) this.tail = node;
1087
+ }
1088
+ evict() {
1089
+ const node = this.tail;
1090
+ if (!node) return;
1091
+ this.detach(node);
1092
+ this.map.delete(node.key);
1093
+ if (this.onEvict) try {
1094
+ this.onEvict(node.key, node.value);
1095
+ } catch {}
1096
+ }
1097
+ clear() {
1098
+ this.map.clear();
1099
+ this.head = this.tail = null;
1100
+ }
1101
+ };
1102
+ //#endregion
1103
+ //#region src/parser/tokenizer.ts
1104
+ const isWhitespace = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
1105
+ function scan(src, cb) {
1106
+ let depth = 0;
1107
+ let inQuote = 0;
1108
+ let start = 0;
1109
+ let i = 0;
1110
+ let pendingSlash = false;
1111
+ const flush = (isComma, isSlash) => {
1112
+ const actualSlash = isSlash || pendingSlash;
1113
+ pendingSlash = false;
1114
+ if (start < i) cb(src.slice(start, i), isComma, actualSlash);
1115
+ else if (isComma) cb("", true, false);
1116
+ else if (actualSlash) cb("", false, true);
1117
+ start = i + 1;
1118
+ };
1119
+ for (; i < src.length; i++) {
1120
+ const ch = src[i];
1121
+ if (inQuote) {
1122
+ if (ch === inQuote && src[i - 1] !== "\\") inQuote = 0;
1123
+ continue;
1124
+ }
1125
+ if (ch === "\"" || ch === "'") {
1126
+ inQuote = ch;
1127
+ continue;
1128
+ }
1129
+ if (ch === "(") {
1130
+ depth++;
1131
+ continue;
1132
+ }
1133
+ if (ch === ")") {
1134
+ if (depth) depth--;
1135
+ continue;
1136
+ }
1137
+ if (!depth) {
1138
+ if (ch === ",") {
1139
+ flush(true, false);
1140
+ continue;
1141
+ }
1142
+ if (ch === "/") {
1143
+ const prevIsWhitespace = isWhitespace(src[i - 1]);
1144
+ const nextIsWhitespace = isWhitespace(src[i + 1]);
1145
+ if (prevIsWhitespace && nextIsWhitespace) {
1146
+ pendingSlash = true;
1147
+ start = i + 1;
1148
+ continue;
1149
+ }
1150
+ continue;
1151
+ }
1152
+ if (isWhitespace(ch)) {
1153
+ flush(false, false);
1154
+ continue;
1155
+ }
1156
+ }
1157
+ }
1158
+ flush(false, false);
1159
+ }
1160
+ //#endregion
1161
+ //#region src/parser/parser.ts
1162
+ var StyleParser = class {
1163
+ opts;
1164
+ cache;
1165
+ constructor(opts = {}) {
1166
+ this.opts = opts;
1167
+ this.cache = new Lru(this.opts.cacheSize ?? 1e3);
1168
+ }
1169
+ process(src) {
1170
+ const key = String(src);
1171
+ const hit = this.cache.get(key);
1172
+ if (hit) return hit;
1173
+ const stripped = foldDslCase(src.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, ""));
1174
+ const groups = [];
1175
+ let currentGroup = makeEmptyDetails();
1176
+ let currentPart = makeEmptyPart();
1177
+ let parts = [];
1178
+ const pushToken = (bucket, processed) => {
1179
+ if (!processed) return;
1180
+ if (currentPart.values.length > 0 && currentPart.values[currentPart.values.length - 1].startsWith("url(")) {
1181
+ currentPart.values[currentPart.values.length - 1] += ` ${processed}`;
1182
+ currentPart.all[currentPart.all.length - 1] += ` ${processed}`;
1183
+ return;
1184
+ }
1185
+ switch (bucket) {
1186
+ case 0:
1187
+ currentPart.colors.push(processed);
1188
+ break;
1189
+ case 1:
1190
+ currentPart.values.push(processed);
1191
+ break;
1192
+ case 2:
1193
+ currentPart.mods.push(processed);
1194
+ break;
1195
+ case 3:
1196
+ currentPart.colors.push(processed);
1197
+ currentPart.values.push(processed);
1198
+ break;
1199
+ }
1200
+ currentPart.all.push(processed);
1201
+ };
1202
+ const endPart = (reset = true) => {
1203
+ if (currentPart.all.length > 0) {
1204
+ currentPart.output = currentPart.all.join(" ");
1205
+ parts.push(currentPart);
1206
+ }
1207
+ if (reset) currentPart = makeEmptyPart();
1208
+ };
1209
+ const endGroup = (reset = true) => {
1210
+ endPart(reset);
1211
+ if (parts.length === 0) parts.push(makeEmptyPart());
1212
+ currentGroup.parts = parts;
1213
+ for (const part of parts) {
1214
+ currentGroup.mods.push(...part.mods);
1215
+ currentGroup.values.push(...part.values);
1216
+ currentGroup.colors.push(...part.colors);
1217
+ currentGroup.all.push(...part.all);
1218
+ }
1219
+ currentGroup.output = parts.length === 1 ? parts[0].output : parts.map((part) => part.output).join(" / ");
1220
+ groups.push(currentGroup);
1221
+ if (reset) {
1222
+ currentGroup = makeEmptyDetails();
1223
+ parts = [];
1224
+ }
1225
+ };
1226
+ scan(stripped, (tok, isComma, isSlash) => {
1227
+ if (tok) {
1228
+ if (currentGroup.input) currentGroup.input += ` ${tok}`;
1229
+ else currentGroup.input = tok;
1230
+ const { bucket, processed } = classify(tok, this.opts, (sub) => this.process(sub));
1231
+ pushToken(bucket, processed);
1232
+ }
1233
+ if (isSlash) endPart();
1234
+ if (isComma) endGroup();
1235
+ });
1236
+ if (currentPart.all.length || parts.length || !groups.length) endGroup(false);
1237
+ const result = {
1238
+ output: groups.length === 1 ? groups[0].output : groups.map((group) => group.output).join(", "),
1239
+ groups
1240
+ };
1241
+ Object.freeze(result);
1242
+ this.cache.set(key, result);
1243
+ return result;
1244
+ }
1245
+ setFunctions(functions) {
1246
+ this.opts.functions = functions;
1247
+ this.cache.clear();
1248
+ }
1249
+ setUnits(units) {
1250
+ this.opts.units = units;
1251
+ this.cache.clear();
1252
+ }
1253
+ updateOptions(patch) {
1254
+ Object.assign(this.opts, patch);
1255
+ if (patch.cacheSize) this.cache = new Lru(patch.cacheSize);
1256
+ else this.cache.clear();
1257
+ }
1258
+ /**
1259
+ * Clear the parser cache.
1260
+ * Call this when external state that affects parsing results has changed
1261
+ * (e.g., predefined tokens).
1262
+ */
1263
+ clearCache() {
1264
+ this.cache.clear();
1265
+ }
1266
+ /**
1267
+ * Get the current units configuration.
1268
+ */
1269
+ getUnits() {
1270
+ return this.opts.units;
1271
+ }
1272
+ };
1273
+ //#endregion
1274
+ //#region src/plugins/color-func.ts
1275
+ const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
1276
+ /**
1277
+ * Parse an angle value with optional unit.
1278
+ * Supports: deg, turn, rad, or unitless (treated as degrees).
1279
+ */
1280
+ const parseAngle = (value) => {
1281
+ const match = value.match(/^([+-]?\d*\.?\d+)(deg|turn|rad)?$/);
1282
+ if (!match) return 0;
1283
+ const num = parseFloat(match[1]);
1284
+ switch (match[2]) {
1285
+ case "turn": return num * 360;
1286
+ case "rad": return num * 180 / Math.PI;
1287
+ default: return num;
1288
+ }
1289
+ };
1290
+ /**
1291
+ * Parse a percentage value (e.g., "50%") to a 0-1 range. A unitless number is
1292
+ * read as the factor it already looks like, so `okhsl(280 .8 .52)` and
1293
+ * `okhsl(280 80% 52%)` are the same color.
1294
+ */
1295
+ const parsePercentage = (value) => {
1296
+ const match = value.match(/^([+-]?\d*\.?\d+)%?$/);
1297
+ if (!match) return 0;
1298
+ const num = parseFloat(match[1]);
1299
+ return value.includes("%") ? num / 100 : num;
1300
+ };
1301
+ /**
1302
+ * Creates a color function handler for the tasty parser.
1303
+ *
1304
+ * A color function is a custom `functions` entry that converts its arguments
1305
+ * to an `rgb(...)` string (an already-supported color), so it needs no special
1306
+ * core integration — registering it as a parse function is enough.
1307
+ *
1308
+ * @param name The name of the color function (e.g., 'okhsl', 'okhst')
1309
+ * @param convert Converts the parsed first three channel values (after angle/
1310
+ * percent normalization) to sRGB `[r, g, b]` in the 0-1 range
1311
+ * @param label Optional diagnostic label shown in dev warnings when the wrong
1312
+ * number of arguments is supplied (e.g., 'H S L'). Has no effect on parsing
1313
+ * or output.
1314
+ */
1315
+ function createColorFunc(name, convert, label) {
1316
+ const conversionCache = new Lru(500);
1317
+ const expected = label ? ` (${label})` : "";
1318
+ return (groups) => {
1319
+ if (groups.length === 0 || groups[0].all.length < 3) {
1320
+ if (isDevEnv()) console.warn(`[Tasty] ${name}(): expected 3 values${expected}, got:`, groups);
1321
+ return "rgb(0% 0% 0%)";
1322
+ }
1323
+ const group = groups[0];
1324
+ const tokens = group.all;
1325
+ const alpha = group.parts.length > 1 && group.parts[1].all.length > 0 ? group.parts[1].output : void 0;
1326
+ const cacheKey = tokens.slice(0, 3).join(" ") + (alpha ? ` / ${alpha}` : "");
1327
+ const cached = conversionCache.get(cacheKey);
1328
+ if (cached) return cached;
1329
+ const h = parseAngle(tokens[0]);
1330
+ const c2 = parsePercentage(tokens[1]);
1331
+ const c3 = parsePercentage(tokens[2]);
1332
+ const misscaled = [tokens[1], tokens[2]].filter((raw, i) => !raw.includes("%") && (i === 0 ? c2 : c3) > 1);
1333
+ if (misscaled.length) warnOnceDev(`${name}:percent-scale`, `${name}(): channels are factors (0-1) or percentages, so ${misscaled.map((v) => `"${v}"`).join(" and ")} ${misscaled.length > 1 ? "clamp" : "clamps"} to 1 — add the missing '%', or write the value as a 0-1 number.`);
1334
+ const [r, g, b] = convert(h, clamp(c2, 0, 1), clamp(c3, 0, 1));
1335
+ const format = (n) => {
1336
+ const pct = n * 100;
1337
+ return parseFloat(pct.toFixed(1)).toString() + "%";
1338
+ };
1339
+ const result = alpha ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})` : `rgb(${format(r)} ${format(g)} ${format(b)})`;
1340
+ conversionCache.set(cacheKey, result);
1341
+ return result;
1342
+ };
1343
+ }
1344
+ //#endregion
1345
+ //#region src/plugins/okhsl-plugin.ts
1346
+ /**
1347
+ * OKHSL Plugin for Tasty
1348
+ *
1349
+ * Converts OKHSL color syntax to RGB notation.
1350
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
1351
+ *
1352
+ * Examples:
1353
+ * okhsl(240.5 50% 50%)
1354
+ * okhsl(240.5deg 50% 50%)
1355
+ * okhsl(0.25turn 50% 50%)
1356
+ * okhsl(1.57rad 50% 50%)
1357
+ */
1358
+ /**
1359
+ * The okhsl function handler for tasty parser.
1360
+ * Receives parsed style groups and returns an RGB color string.
1361
+ */
1362
+ const okhslFunction = createColorFunc("okhsl", okhslToSrgb, "H S L");
1363
+ /**
1364
+ * OKHSL Plugin for Tasty.
1365
+ *
1366
+ * Adds support for the `okhsl()` color function in tasty styles.
1367
+ *
1368
+ * @example
1369
+ * ```ts
1370
+ * import { configure } from '@tenphi/tasty';
1371
+ * import { okhslPlugin } from '@tenphi/tasty';
1372
+ *
1373
+ * configure({
1374
+ * plugins: [okhslPlugin()],
1375
+ * });
1376
+ *
1377
+ * // Now you can use okhsl in styles:
1378
+ * const Box = tasty({
1379
+ * styles: {
1380
+ * fill: 'okhsl(240 50% 50%)',
1381
+ * },
1382
+ * });
1383
+ * ```
1384
+ */
1385
+ const okhslPlugin = () => ({
1386
+ name: "okhsl",
1387
+ functions: { okhsl: okhslFunction }
1388
+ });
1389
+ //#endregion
1390
+ //#region src/plugins/okhst-plugin.ts
1391
+ /**
1392
+ * OKHST Plugin for Tasty
1393
+ *
1394
+ * Converts OKHST color syntax to RGB notation.
1395
+ * OKHST is OKHSL with the lightness axis replaced by a contrast-uniform tone axis.
1396
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
1397
+ *
1398
+ * Examples:
1399
+ * okhst(240.5 50% 50%)
1400
+ * okhst(240.5deg 50% 50%)
1401
+ * okhst(0.25turn 50% 50%)
1402
+ * okhst(1.57rad 50% 50%)
1403
+ */
1404
+ /**
1405
+ * The okhst function handler for tasty parser.
1406
+ * Receives parsed style groups and returns an RGB color string.
1407
+ */
1408
+ const okhstFunction = createColorFunc("okhst", okhstToSrgb, "H S T");
1409
+ /**
1410
+ * OKHST Plugin for Tasty.
1411
+ *
1412
+ * Adds support for the `okhst()` color function in tasty styles.
1413
+ *
1414
+ * @example
1415
+ * ```ts
1416
+ * import { configure } from '@tenphi/tasty';
1417
+ * import { okhstPlugin } from '@tenphi/tasty';
1418
+ *
1419
+ * configure({
1420
+ * plugins: [okhstPlugin()],
1421
+ * });
1422
+ *
1423
+ * // Now you can use okhst in styles:
1424
+ * const Box = tasty({
1425
+ * styles: {
1426
+ * fill: 'okhst(240 50% 50%)',
1427
+ * },
1428
+ * });
1429
+ * ```
1430
+ */
1431
+ const okhstPlugin = () => ({
1432
+ name: "okhst",
1433
+ functions: { okhst: okhstFunction }
1434
+ });
1435
+ //#endregion
1436
+ //#region src/plugins/defaults.ts
1437
+ /**
1438
+ * Register the default color functions (okhsl, okhst) with the global parser.
1439
+ *
1440
+ * They are ordinary `functions` entries — the same mechanism any third-party
1441
+ * color plugin uses — so they need no special-casing in core.
1442
+ *
1443
+ * Idempotent and safe to call from the lazy parser initializer so that
1444
+ * zero-config usage keeps working. `resetGlobalParseFunctions()` re-invokes it
1445
+ * after clearing user-registered functions so the defaults survive a config reset.
1446
+ *
1447
+ * @internal
1448
+ */
1449
+ function registerDefaultFunctions() {
1450
+ customFunction("okhsl", okhslFunction);
1451
+ customFunction("okhst", okhstFunction);
1452
+ }
1453
+ //#endregion
1454
+ //#region src/utils/styles.ts
1455
+ /**
1456
+ * Normalize a color token value.
1457
+ * - Boolean `true` is converted to `'transparent'`
1458
+ * - Boolean `false` returns `null` (signals the token should be skipped)
1459
+ * - Other values are returned as-is
1460
+ *
1461
+ * @param value - The raw token value
1462
+ * @returns Normalized value or null if the token should be skipped
1463
+ */
1464
+ function normalizeColorTokenValue(value) {
1465
+ if (value === true) return "transparent";
1466
+ if (value === false) return null;
1467
+ return value;
1468
+ }
1469
+ const COLOR_VAR_PATTERN = /^var\(--([a-z0-9-]+)-color[,)]/;
1470
+ const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
1471
+ const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
1472
+ const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
1473
+ function isSimpleColorFast(val) {
1474
+ switch (val.charCodeAt(0)) {
1475
+ case 35: return RE_HEX_COLOR.test(val);
1476
+ case 114:
1477
+ if (val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98) return true;
1478
+ break;
1479
+ case 104:
1480
+ if (val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108) return true;
1481
+ break;
1482
+ case 108:
1483
+ if (val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104) return true;
1484
+ break;
1485
+ case 111:
1486
+ if (val.startsWith("oklch(")) return true;
1487
+ break;
1488
+ case 118:
1489
+ if (RE_VAR_COLOR.test(val)) return true;
1490
+ break;
1491
+ case 99:
1492
+ if (val === "currentColor" || val === "currentcolor") return true;
1493
+ break;
1494
+ case 116:
1495
+ if (val === "transparent") return true;
1496
+ break;
1497
+ }
1498
+ return getNamedColorHex().has(val.toLowerCase());
1499
+ }
1500
+ let colorWarningCount = 0;
1501
+ const MAX_COLOR_WARNINGS = 10;
1502
+ const CUSTOM_UNITS = {
1503
+ r: "6px",
1504
+ cr: "10px",
1505
+ bw: "1px",
1506
+ ow: "3px",
1507
+ x: "8px",
1508
+ sf: function sf(num) {
1509
+ return `minmax(0, ${num}fr)`;
1510
+ }
1511
+ };
1512
+ const DIRECTIONS = [
1513
+ "top",
1514
+ "right",
1515
+ "bottom",
1516
+ "left"
1517
+ ];
1518
+ let __tastyParser = null;
1519
+ function getOrCreateParser() {
1520
+ if (!__tastyParser) {
1521
+ __tastyParser = new StyleParser({ units: CUSTOM_UNITS });
1522
+ __tastyParser.setFunctions(__tastyParseFunctions);
1523
+ registerDefaultFunctions();
1524
+ }
1525
+ return __tastyParser;
1526
+ }
1527
+ const __tastyParseFunctions = {};
1528
+ /**
1529
+ * Register a parse-time function.
1530
+ *
1531
+ * @internal Use `configure({ functions })`. This bypasses the configuration lock
1532
+ * and is kept only for the lazy default-function bootstrap and the `@function`
1533
+ * polyfill, both of which must register after `configure()` has run.
1534
+ */
1535
+ function customFunction(name, fn) {
1536
+ __tastyParseFunctions[name] = fn;
1537
+ getOrCreateParser().setFunctions(__tastyParseFunctions);
1538
+ }
1539
+ /**
1540
+ * Reset parse functions back to the built-in set (okhsl/okhst).
1541
+ *
1542
+ * @internal Called by `resetConfig()`.
1543
+ */
1544
+ function resetGlobalParseFunctions() {
1545
+ for (const key of Object.keys(__tastyParseFunctions)) delete __tastyParseFunctions[key];
1546
+ registerDefaultFunctions();
1547
+ getOrCreateParser().setFunctions(__tastyParseFunctions);
1548
+ }
1549
+ /**
1550
+ * Get the global StyleParser instance.
1551
+ * Used by configure() to apply parser configuration.
1552
+ */
1553
+ function getGlobalParser() {
1554
+ return getOrCreateParser();
1555
+ }
1556
+ /**
1557
+ * Get the current custom functions registry.
1558
+ * Used by configure() to merge with new functions.
1559
+ */
1560
+ /**
1561
+ * The live parse-function registry.
1562
+ *
1563
+ * @internal Returns the mutable internal map; writing to it bypasses the
1564
+ * parser's cache invalidation, so the write silently does not take effect.
1565
+ * Read-only, internal use.
1566
+ */
1567
+ function getGlobalParseFunctions() {
1568
+ return __tastyParseFunctions;
1569
+ }
1570
+ /**
1571
+ * Storage for predefined tokens that are replaced during style parsing.
1572
+ * Keys are token names (with $ or # prefix), values are pre-processed CSS values.
1573
+ */
1574
+ let __globalPredefinedTokens = null;
1575
+ /**
1576
+ * Set global predefined tokens.
1577
+ * Called from configure() after processing token values.
1578
+ * Merges with existing tokens (new tokens override existing ones with same key).
1579
+ * Keys are normalized to lowercase (parser lowercases input before classification).
1580
+ * @internal
1581
+ */
1582
+ function setGlobalPredefinedTokens(tokens) {
1583
+ const normalizedTokens = {};
1584
+ for (const [key, value] of Object.entries(tokens)) {
1585
+ const lowerKey = key.toLowerCase();
1586
+ const lowerValue = value.toLowerCase();
1587
+ if (lowerKey.startsWith("#") && lowerValue === "#current") {
1588
+ console.warn(`[Tasty] Using #current to define color token "${key}" is not supported. The #current token represents currentcolor which cannot be used as a base for other tokens.`);
1589
+ continue;
1590
+ }
1591
+ normalizedTokens[lowerKey] = value;
1592
+ }
1593
+ __globalPredefinedTokens = __globalPredefinedTokens ? {
1594
+ ...__globalPredefinedTokens,
1595
+ ...normalizedTokens
1596
+ } : normalizedTokens;
1597
+ getOrCreateParser().clearCache();
1598
+ }
1599
+ /**
1600
+ * Get the current global predefined tokens.
1601
+ * Returns null if no tokens are configured.
1602
+ */
1603
+ function getGlobalPredefinedTokens() {
1604
+ return __globalPredefinedTokens;
1605
+ }
1606
+ /**
1607
+ * Reset global predefined tokens.
1608
+ * Used for testing.
1609
+ * @internal
1610
+ */
1611
+ function resetGlobalPredefinedTokens() {
1612
+ __globalPredefinedTokens = null;
1613
+ getOrCreateParser().clearCache();
1614
+ }
1615
+ /**
1616
+ *
1617
+ * @param {String} value
1618
+ * @param {Number} mode
1619
+ * @returns {Object<String,String|Array>}
1620
+ */
1621
+ function parseStyle(value) {
1622
+ let str;
1623
+ if (typeof value === "string") str = value;
1624
+ else if (typeof value === "number") str = String(value);
1625
+ else str = "";
1626
+ return getOrCreateParser().process(str);
1627
+ }
1628
+ /** A `$`-prefixed custom-property reference — the only DSL syntax that starts with `$`. */
1629
+ const RE_CUSTOM_PROPERTY_REF = /\$[a-zA-Z_$]/;
1630
+ /**
1631
+ * Substitute custom-property references (`$ident` → `var(--ident)`) inside a
1632
+ * value a handler would otherwise emit to CSS verbatim.
1633
+ *
1634
+ * Handlers for keyword-valued properties (`display`, `align`, `text-transform`,
1635
+ * `overflow`, …) pass their input straight through, since there are no units or
1636
+ * color tokens to resolve. That leaked the raw DSL into the stylesheet:
1637
+ * `display: '$my-display'` emitted `display: $my-display`, which the browser
1638
+ * drops as invalid. The same applies to color props whose value the parser could
1639
+ * not classify as a color — `$ident` is a color only when the name ends with
1640
+ * `-color`, so `fill: '$my-fill'` fell back to the unparsed input.
1641
+ *
1642
+ * Values without a `$` skip the parser entirely, so pass-through behaviour (and
1643
+ * the parser's case folding) is unchanged for every value that was already
1644
+ * valid CSS.
1645
+ */
1646
+ function resolveCustomProperties(value) {
1647
+ if (!RE_CUSTOM_PROPERTY_REF.test(value)) return value;
1648
+ return parseStyle(value).output || value;
1649
+ }
1650
+ /**
1651
+ * Parse color. Find it value, name and opacity.
1652
+ * Optimized to avoid heavy parseStyle calls for simple color patterns.
1653
+ */
1654
+ function parseColor(val, ignoreError = false) {
1655
+ if (typeof val !== "string") val = String(val ?? "");
1656
+ val = val.trim();
1657
+ if (!val) return {};
1658
+ const isSimpleColor = isSimpleColorFast(val);
1659
+ let firstColor;
1660
+ if (isSimpleColor) firstColor = val;
1661
+ else {
1662
+ const extractedColor = parseStyle(val).groups.find((g) => g.colors.length)?.colors[0];
1663
+ if (!extractedColor) {
1664
+ if (!ignoreError && colorWarningCount < MAX_COLOR_WARNINGS) {
1665
+ console.warn("[Tasty] unable to parse color:", val);
1666
+ colorWarningCount++;
1667
+ if (colorWarningCount === MAX_COLOR_WARNINGS) console.warn("[Tasty] color parsing warnings will be suppressed from now on");
1668
+ }
1669
+ return {};
1670
+ }
1671
+ firstColor = extractedColor;
1672
+ }
1673
+ const faded = parseAlphaOverride(firstColor);
1674
+ const baseColor = faded ? faded.color : firstColor;
1675
+ const nameMatch = baseColor.match(COLOR_VAR_PATTERN);
1676
+ let opacity;
1677
+ if (faded) {
1678
+ const { alpha } = faded;
1679
+ const v = parseFloat(alpha);
1680
+ if (!isNaN(v)) opacity = alpha.endsWith("%") ? v : v * 100;
1681
+ } else if (baseColor.startsWith("rgb") || baseColor.startsWith("hsl") || baseColor.startsWith("lch") || baseColor.startsWith("oklch")) {
1682
+ const alphaMatch = baseColor.match(RGB_ALPHA_PATTERN);
1683
+ if (alphaMatch) {
1684
+ const v = parseFloat(alphaMatch[1]);
1685
+ if (!isNaN(v)) opacity = v * 100;
1686
+ }
1687
+ }
1688
+ return {
1689
+ color: firstColor,
1690
+ name: nameMatch ? nameMatch[1] : void 0,
1691
+ opacity
1692
+ };
1693
+ }
1694
+ function filterMods(mods, allowedMods) {
1695
+ const filtered = [];
1696
+ for (const mod of mods) if (allowedMods.includes(mod)) filtered.push(mod);
1697
+ return filtered;
1698
+ }
1699
+ const _innerCache = /* @__PURE__ */ new WeakMap();
1700
+ const _topLevelCache = /* @__PURE__ */ new WeakMap();
1701
+ function stringifyStyles(styles) {
1702
+ if (styles == null || typeof styles !== "object") return "";
1703
+ const cached = _topLevelCache.get(styles);
1704
+ if (cached !== void 0) return cached;
1705
+ const obj = styles;
1706
+ const keys = Object.keys(obj).sort();
1707
+ const parts = [];
1708
+ for (const k of keys) {
1709
+ const v = obj[k];
1710
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") continue;
1711
+ const c0 = k.charCodeAt(0);
1712
+ const needsInnerSort = (c0 >= 65 && c0 <= 90 || c0 === 38) && v && typeof v === "object" && !Array.isArray(v);
1713
+ let sv;
1714
+ if (needsInnerSort) {
1715
+ sv = _innerCache.get(v);
1716
+ if (sv === void 0) {
1717
+ const innerObj = v;
1718
+ const innerKeys = Object.keys(innerObj).sort();
1719
+ const innerParts = [];
1720
+ for (const ik of innerKeys) {
1721
+ const ivs = JSON.stringify(innerObj[ik]);
1722
+ if (ivs !== void 0) innerParts.push(JSON.stringify(ik) + ":" + ivs);
1723
+ }
1724
+ sv = "{" + innerParts.join(",") + "}";
1725
+ _innerCache.set(v, sv);
1726
+ }
1727
+ } else sv = JSON.stringify(v);
1728
+ parts.push(JSON.stringify(k) + ":" + sv);
1729
+ }
1730
+ const result = "{" + parts.join(",") + "}";
1731
+ _topLevelCache.set(styles, result);
1732
+ return result;
1733
+ }
1734
+ //#endregion
1735
+ //#region src/utils/function-color.ts
1736
+ const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
1737
+ const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
1738
+ /**
1739
+ * Resolve a `name(...)` value produced by a registered custom parse function
1740
+ * into its concrete color output.
1741
+ *
1742
+ * A color function is just a `functions` entry whose output is an already
1743
+ * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
1744
+ * value to the global parser (which already runs the registered parse function)
1745
+ * and returns the result only when it looks like a color. Returns `null` when
1746
+ * `str` is not a registered custom function or its output is not a color.
1747
+ *
1748
+ * This is the generic replacement for the previously hardcoded okhsl/okhst
1749
+ * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
1750
+ * the `#token.alpha` injection path.
1751
+ */
1752
+ function resolveFunctionColor(str) {
1753
+ const m = RE_FUNC_NAME.exec(str);
1754
+ if (!m) return null;
1755
+ const name = m[1].toLowerCase();
1756
+ getGlobalParser();
1757
+ if (!(name in getGlobalParseFunctions())) return null;
1758
+ const out = getGlobalParser().process(str).output;
1759
+ if (!out || !RE_COLOR_OUT.test(out)) return null;
1760
+ return out;
1761
+ }
1762
+ //#endregion
1763
+ //#region src/utils/colors.ts
1764
+ function color(name, opacity = 1) {
1765
+ if (opacity !== 1) return overrideColorAlpha(`var(--${name}-color)`, String(opacity));
1766
+ return `var(--${name}-color)`;
1767
+ }
1768
+ //#endregion
1769
+ export { strToRgb as A, StyleParser as C, getRgbValuesFromRgbaString as D, getNamedColorHex as E, RE_RAW_UNIT as F, toSnakeCase as M, CSS_WIDE_KEYWORDS as N, hexToRgb as O, RE_NUMBER as P, createColorFunc as S, makeEmptyDetails as T, stringifyStyles as _, customFunction as a, okhslFunction as b, getGlobalParser as c, parseColor as d, parseStyle as f, setGlobalPredefinedTokens as g, resolveCustomProperties as h, DIRECTIONS as i, normalizeDslName as j, hslToRgbValues as k, getGlobalPredefinedTokens as l, resetGlobalPredefinedTokens as m, resolveFunctionColor as n, filterMods as o, resetGlobalParseFunctions as p, CUSTOM_UNITS as r, getGlobalParseFunctions as s, color as t, normalizeColorTokenValue as u, okhstFunction as v, Lru as w, okhslPlugin as x, okhstPlugin as y };
1770
+
1771
+ //# sourceMappingURL=dsl-D7kBow--.js.map