@tenphi/tasty 3.0.2 → 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 (34) hide show
  1. package/dist/{collector-BpkjNQDo.js → collector-D7yzmG5G.js} +3 -3
  2. package/dist/{collector-BpkjNQDo.js.map → collector-D7yzmG5G.js.map} +1 -1
  3. package/dist/{config-DOCuTykY.js → config-CwQ-fAsp.js} +403 -212
  4. package/dist/config-CwQ-fAsp.js.map +1 -0
  5. package/dist/core/index.js +5 -5
  6. package/dist/{core-p6iTbQnn.js → core-CqSh853z.js} +9 -10
  7. package/dist/core-CqSh853z.js.map +1 -0
  8. package/dist/{css-writer-BHjTF0YN.js → css-writer-dS1srTkS.js} +3 -3
  9. package/dist/{css-writer-BHjTF0YN.js.map → css-writer-dS1srTkS.js.map} +1 -1
  10. package/dist/{format-rules-Cb0YIjyS.js → format-rules-CaW4lJGg.js} +11 -9
  11. package/dist/format-rules-CaW4lJGg.js.map +1 -0
  12. package/dist/{hydrate-xnaB3SDm.js → hydrate-uFv9kx7G.js} +2 -2
  13. package/dist/{hydrate-xnaB3SDm.js.map → hydrate-uFv9kx7G.js.map} +1 -1
  14. package/dist/index.js +6 -6
  15. package/dist/{keyframes-DiXjNoBZ.js → keyframes-DaZhjqkd.js} +2 -2
  16. package/dist/{keyframes-DiXjNoBZ.js.map → keyframes-DaZhjqkd.js.map} +1 -1
  17. package/dist/{merge-styles-B0lMso5W.js → merge-styles-H8LFJ5HY.js} +2 -2
  18. package/dist/{merge-styles-B0lMso5W.js.map → merge-styles-H8LFJ5HY.js.map} +1 -1
  19. package/dist/{resolve-recipes-C9nAuwgR.js → resolve-recipes-aN94fjqS.js} +3 -3
  20. package/dist/{resolve-recipes-C9nAuwgR.js.map → resolve-recipes-aN94fjqS.js.map} +1 -1
  21. package/dist/ssr/astro-client.js +1 -1
  22. package/dist/ssr/astro.js +3 -3
  23. package/dist/ssr/index.js +3 -3
  24. package/dist/ssr/next.js +4 -4
  25. package/dist/static/index.js +1 -1
  26. package/dist/zero/babel.js +4 -4
  27. package/dist/zero/index.js +1 -1
  28. package/docs/configuration.md +10 -6
  29. package/docs/dsl.md +99 -2
  30. package/docs/styles.md +3 -1
  31. package/package.json +6 -6
  32. package/dist/config-DOCuTykY.js.map +0 -1
  33. package/dist/core-p6iTbQnn.js.map +0 -1
  34. package/dist/format-rules-Cb0YIjyS.js.map +0 -1
@@ -18,6 +18,23 @@ const CSS_WIDE_KEYWORDS = new Set([
18
18
  "unset",
19
19
  "revert-layer"
20
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
+ */
21
38
  const COLOR_FUNCS = new Set([
22
39
  "rgb",
23
40
  "rgba",
@@ -31,9 +48,20 @@ const COLOR_FUNCS = new Set([
31
48
  "color",
32
49
  "device-cmyk",
33
50
  "gray",
34
- "color-mix",
35
- "color-contrast"
51
+ ...DERIVED_COLOR_FUNCS_LIST
36
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
+ }
37
65
  const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
38
66
  const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
39
67
  const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
@@ -110,141 +138,6 @@ function foldDslCase(src) {
110
138
  return out + src.slice(last).toLowerCase();
111
139
  }
112
140
  //#endregion
113
- //#region src/parser/lru.ts
114
- var Lru = class {
115
- limit;
116
- map = /* @__PURE__ */ new Map();
117
- head = null;
118
- tail = null;
119
- onEvict;
120
- constructor(limit = 1e3, onEvict) {
121
- this.limit = limit;
122
- let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
123
- if (normalized <= 0) normalized = 1e3;
124
- this.limit = normalized;
125
- this.onEvict = onEvict;
126
- }
127
- setOnEvict(fn) {
128
- this.onEvict = fn;
129
- }
130
- get(key) {
131
- const node = this.map.get(key);
132
- if (!node) return void 0;
133
- this.touch(key, node);
134
- return node.value;
135
- }
136
- set(key, value) {
137
- let node = this.map.get(key);
138
- if (node) {
139
- node.value = value;
140
- this.touch(key, node);
141
- return;
142
- }
143
- node = {
144
- prev: null,
145
- next: this.head,
146
- value
147
- };
148
- if (this.head) {
149
- const headNode = this.map.get(this.head);
150
- if (headNode) headNode.prev = key;
151
- }
152
- this.head = key;
153
- if (!this.tail) this.tail = key;
154
- this.map.set(key, node);
155
- if (this.map.size > this.limit) this.evict();
156
- }
157
- delete(key) {
158
- const node = this.map.get(key);
159
- if (!node) return;
160
- if (node.prev) {
161
- const prevNode = this.map.get(node.prev);
162
- if (prevNode) prevNode.next = node.next;
163
- }
164
- if (node.next) {
165
- const nextNode = this.map.get(node.next);
166
- if (nextNode) nextNode.prev = node.prev;
167
- }
168
- if (this.head === key) this.head = node.next;
169
- if (this.tail === key) this.tail = node.prev;
170
- this.map.delete(key);
171
- }
172
- keys() {
173
- return this.map.keys();
174
- }
175
- touch(key, node) {
176
- if (this.head === key) return;
177
- if (node.prev) {
178
- const prevNode = this.map.get(node.prev);
179
- if (prevNode) prevNode.next = node.next;
180
- }
181
- if (node.next) {
182
- const nextNode = this.map.get(node.next);
183
- if (nextNode) nextNode.prev = node.prev;
184
- }
185
- if (this.tail === key) this.tail = node.prev;
186
- node.prev = null;
187
- node.next = this.head;
188
- if (this.head) {
189
- const headNode = this.map.get(this.head);
190
- if (headNode) headNode.prev = key;
191
- }
192
- this.head = key;
193
- }
194
- evict() {
195
- const old = this.tail;
196
- if (!old) return;
197
- const node = this.map.get(old);
198
- if (!node) {
199
- if (this.head === old) this.head = null;
200
- this.tail = null;
201
- return;
202
- }
203
- if (node.prev) {
204
- const prevNode = this.map.get(node.prev);
205
- if (prevNode) prevNode.next = null;
206
- }
207
- this.tail = node.prev;
208
- if (this.head === old) this.head = null;
209
- this.map.delete(old);
210
- if (this.onEvict) try {
211
- this.onEvict(old, node.value);
212
- } catch {}
213
- }
214
- clear() {
215
- this.map.clear();
216
- this.head = this.tail = null;
217
- }
218
- };
219
- //#endregion
220
- //#region src/utils/function-color.ts
221
- const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
222
- const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
223
- /**
224
- * Resolve a `name(...)` value produced by a registered custom parse function
225
- * into its concrete color output.
226
- *
227
- * A color function is just a `functions` entry whose output is an already
228
- * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
229
- * value to the global parser (which already runs the registered parse function)
230
- * and returns the result only when it looks like a color. Returns `null` when
231
- * `str` is not a registered custom function or its output is not a color.
232
- *
233
- * This is the generic replacement for the previously hardcoded okhsl/okhst
234
- * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
235
- * the `#token.alpha` injection path.
236
- */
237
- function resolveFunctionColor(str) {
238
- const m = RE_FUNC_NAME.exec(str);
239
- if (!m) return null;
240
- const name = m[1].toLowerCase();
241
- getGlobalParser();
242
- if (!(name in getGlobalParseFunctions())) return null;
243
- const out = getGlobalParser().process(str).output;
244
- if (!out || !RE_COLOR_OUT.test(out)) return null;
245
- return out;
246
- }
247
- //#endregion
248
141
  //#region src/utils/color-math.ts
249
142
  const OKLab_to_LMS_M = [
250
143
  [
@@ -984,6 +877,141 @@ function oklchStringToRgb(oklchStr) {
984
877
  return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
985
878
  }
986
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.
1003
+ */
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;
1013
+ }
1014
+ //#endregion
987
1015
  //#region src/utils/color-space.ts
988
1016
  let currentColorSpace = "oklch";
989
1017
  const colorSpaceCache = new Lru(500);
@@ -1003,9 +1031,6 @@ function resetColorSpace() {
1003
1031
  function getColorSpaceSuffix() {
1004
1032
  return currentColorSpace;
1005
1033
  }
1006
- function getColorSpaceFunc() {
1007
- return currentColorSpace;
1008
- }
1009
1034
  function formatNum(n, precision) {
1010
1035
  return parseFloat(n.toFixed(precision)).toString();
1011
1036
  }
@@ -1262,6 +1287,91 @@ function strToColorSpace(color) {
1262
1287
  return result;
1263
1288
  }
1264
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
+ /**
1265
1375
  * Extract the decomposed components of a color in the configured color space.
1266
1376
  * Returns a space-separated string of components without the wrapping function.
1267
1377
  * Alpha is NOT included — components are used for alpha composition via `/ alpha`.
@@ -1282,6 +1392,41 @@ function getColorSpaceComponents(color) {
1282
1392
  return result;
1283
1393
  }
1284
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
+ /**
1285
1430
  * Convert a color initial value (from @property definitions) to components
1286
1431
  * in the configured color space.
1287
1432
  */
@@ -1354,6 +1499,53 @@ const finalizeGroup = (d, parts) => {
1354
1499
  //#endregion
1355
1500
  //#region src/parser/classify.ts
1356
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
+ /**
1357
1549
  * Re-parses a value through the parser until it stabilizes (no changes)
1358
1550
  * or max iterations reached. This allows units to reference other units.
1359
1551
  * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'
@@ -1455,17 +1647,10 @@ function classify(raw, opts, recurse) {
1455
1647
  processed: "currentcolor"
1456
1648
  };
1457
1649
  const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1458
- if (currentAlphaMatch) {
1459
- const rawAlpha = currentAlphaMatch[1];
1460
- let percentage;
1461
- if (rawAlpha.startsWith("$")) percentage = `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
1462
- else if (rawAlpha === "0") percentage = "0%";
1463
- else percentage = `${parseFloat("." + rawAlpha) * 100}%`;
1464
- return {
1465
- bucket: 0,
1466
- processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`
1467
- };
1468
- }
1650
+ if (currentAlphaMatch) return {
1651
+ bucket: 0,
1652
+ processed: fadeCurrentColor(currentAlphaMatch[1])
1653
+ };
1469
1654
  if (token[0] === "$" || token[0] === "#") {
1470
1655
  const predefinedTokens = getGlobalPredefinedTokens();
1471
1656
  if (predefinedTokens) {
@@ -1482,10 +1667,14 @@ function classify(raw, opts, recurse) {
1482
1667
  if (baseKey in predefinedTokens) {
1483
1668
  const resolvedValue = predefinedTokens[baseKey];
1484
1669
  if (resolvedValue.startsWith("#")) return classify(`${foldDslCase(resolvedValue)}.${rawAlpha}`, opts, recurse);
1485
- const funcMatch = resolvedValue.match(/^([a-z][a-z0-9-]*)\((.+)\)$/i);
1670
+ const funcMatch = resolvedValue.match(RE_FUNC_CALL);
1486
1671
  if (funcMatch) {
1487
1672
  const [, funcName, args] = funcMatch;
1488
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
+ };
1489
1678
  const isCustomFunc = !!(opts.functions && lowerFunc in opts.functions && !COLOR_FUNCS.has(lowerFunc) && !COLOR_FUNCS.has(funcName.replace(/a$/i, "").toLowerCase()));
1490
1679
  const normalizedFunc = isCustomFunc ? lowerFunc : funcName.replace(/a$/i, "").toLowerCase();
1491
1680
  if (!(COLOR_FUNCS.has(normalizedFunc) || COLOR_FUNCS.has(lowerFunc) || isCustomFunc)) return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
@@ -1573,13 +1762,9 @@ function classify(raw, opts, recurse) {
1573
1762
  const alphaMatch = token.match(/^#([a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1574
1763
  if (alphaMatch) {
1575
1764
  const [, base, rawAlpha] = alphaMatch;
1576
- let alpha;
1577
- if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
1578
- else if (rawAlpha === "0") alpha = "0";
1579
- else alpha = `.${rawAlpha}`;
1580
1765
  return {
1581
1766
  bucket: 0,
1582
- processed: `${getColorSpaceFunc()}(var(--${base}-color-${getColorSpaceSuffix()}) / ${alpha})`
1767
+ processed: fadeColor(`var(--${base}-color)`, rawAlpha)
1583
1768
  };
1584
1769
  }
1585
1770
  const name = token.slice(1);
@@ -1597,10 +1782,12 @@ function classify(raw, opts, recurse) {
1597
1782
  const fname = token.slice(0, openIdx);
1598
1783
  const inner = token.slice(openIdx + 1, -1);
1599
1784
  if (COLOR_FUNCS.has(fname)) {
1600
- 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})`;
1601
1788
  return {
1602
- bucket: 0,
1603
- processed: `${canonicalFuncName(fname)}(${argProcessed})`
1789
+ bucket: fname === "light-dark" && !hasColorArgs(parsedInner) ? 1 : 0,
1790
+ processed
1604
1791
  };
1605
1792
  }
1606
1793
  if (opts.functions && fname in opts.functions) {
@@ -2076,8 +2263,8 @@ function normalizeColorTokenValue(value) {
2076
2263
  if (value === false) return null;
2077
2264
  return value;
2078
2265
  }
2079
- const COLOR_VAR_PATTERN = /var\(--([a-z0-9-]+)-color/;
2080
- 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)[,)]/;
2081
2268
  const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
2082
2269
  const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
2083
2270
  const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
@@ -2267,11 +2454,17 @@ function parseColor(val, ignoreError = false) {
2267
2454
  }
2268
2455
  firstColor = extractedColor;
2269
2456
  }
2270
- let nameMatch = firstColor.match(COLOR_VAR_PATTERN);
2271
- 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);
2272
2461
  let opacity;
2273
- if (firstColor.startsWith("rgb") || firstColor.startsWith("hsl") || firstColor.startsWith("lch") || firstColor.startsWith("oklch")) {
2274
- 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);
2275
2468
  if (alphaMatch) {
2276
2469
  const v = parseFloat(alphaMatch[1]);
2277
2470
  if (!isNaN(v)) opacity = v * 100;
@@ -3239,6 +3432,10 @@ function rscClassRegexGlobal(prefix) {
3239
3432
  */
3240
3433
  const CUSTOM_PROP_DECL = /^\s*(--[a-z0-9_-]+)\s*:\s*(.+?)\s*$/i;
3241
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.+-]+$/;
3242
3439
  var PropertyTypeResolver = class {
3243
3440
  /** propName → the prop it depends on */
3244
3441
  pendingDeps = /* @__PURE__ */ new Map();
@@ -3251,6 +3448,15 @@ var PropertyTypeResolver = class {
3251
3448
  scanDeclarations(declarations, isPropertyDefined, registerProperty) {
3252
3449
  if (!declarations.includes("--")) return;
3253
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
+ }
3254
3460
  for (const part of parts) {
3255
3461
  if (!part.trim()) continue;
3256
3462
  const match = CUSTOM_PROP_DECL.exec(part);
@@ -3553,27 +3759,28 @@ function borderStyle({ border }) {
3553
3759
  }
3554
3760
  borderStyle.__lookupStyles = ["border"];
3555
3761
  //#endregion
3556
- //#region src/styles/createStyle.ts
3557
- const CACHE = {};
3558
- /**
3559
- * Convert color fallback chain to component fallback chain.
3560
- * Example: var(--primary-color, var(--secondary-color))
3561
- * → var(--primary-color-oklch, var(--secondary-color-oklch))
3562
- */
3563
- function convertColorChainToComponentChain(colorValue) {
3564
- const suffix = getColorSpaceSuffix();
3565
- const componentVarMatch = colorValue.match(/^(?:rgb|hsl|oklch)a?\(\s*(var\(--[a-z0-9-]+-color-(?:rgb|hsl|oklch)\))\s*\//);
3566
- if (componentVarMatch) return componentVarMatch[1];
3567
- const match = colorValue.match(/var\(--([a-z0-9-]+)-color\s*(?:,\s*(.+))?\)/);
3568
- if (!match) {
3569
- const components = getColorSpaceComponents(colorValue);
3570
- if (components !== colorValue) return components;
3571
- return colorValue;
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
+ });
3572
3777
  }
3573
- const [, name, fallback] = match;
3574
- if (!fallback) return `var(--${name}-color-${suffix})`;
3575
- return `var(--${name}-color-${suffix}, ${convertColorChainToComponentChain(fallback.trim())})`;
3778
+ return styles;
3576
3779
  }
3780
+ colorStyle.__lookupStyles = ["color"];
3781
+ //#endregion
3782
+ //#region src/styles/createStyle.ts
3783
+ const CACHE = {};
3577
3784
  function createStyle(styleName, cssStyle, converter) {
3578
3785
  const key = `${styleName}.${cssStyle ?? ""}`;
3579
3786
  if (!CACHE[key]) {
@@ -3617,6 +3824,10 @@ function createStyle(styleName, cssStyle, converter) {
3617
3824
  [finalCssStyle]: colorSpaceStr,
3618
3825
  [`${finalCssStyle}-${suffix}`]: getColorSpaceComponents(colorSpaceStr)
3619
3826
  };
3827
+ if (color && colorFuncName(color)) return {
3828
+ [finalCssStyle]: color,
3829
+ [`${finalCssStyle}-${suffix}`]: convertColorChainToComponentChain(color)
3830
+ };
3620
3831
  return { [finalCssStyle]: color ?? "" };
3621
3832
  }
3622
3833
  const processed = parseStyle(styleValue);
@@ -3628,27 +3839,6 @@ function createStyle(styleName, cssStyle, converter) {
3628
3839
  return CACHE[key];
3629
3840
  }
3630
3841
  //#endregion
3631
- //#region src/styles/color.ts
3632
- function colorStyle({ color }) {
3633
- if (!color) return null;
3634
- if (color === true) color = "currentColor";
3635
- if (typeof color === "string" && (color.startsWith("#") || color.startsWith("(#"))) color = parseColor(color).color || color;
3636
- else if (typeof color === "string") color = resolveCustomProperties(color);
3637
- const match = color.match(/var\(--(.+?)-color/);
3638
- let name = "";
3639
- if (match) name = match[1];
3640
- const styles = { color };
3641
- if (name && name !== "current") {
3642
- const suffix = getColorSpaceSuffix();
3643
- Object.assign(styles, {
3644
- "--current-color": color,
3645
- [`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
3646
- });
3647
- }
3648
- return styles;
3649
- }
3650
- colorStyle.__lookupStyles = ["color"];
3651
- //#endregion
3652
3842
  //#region src/styles/display.ts
3653
3843
  /**
3654
3844
  * Process textOverflow into CSS properties for truncation/clamping.
@@ -4638,10 +4828,10 @@ function scrollbarStyle({ scrollbar, overflow }) {
4638
4828
  scrollbarStyle.__lookupStyles = ["scrollbar", "overflow"];
4639
4829
  //#endregion
4640
4830
  //#region src/styles/shadow.ts
4641
- function toBoxShadow(shadow) {
4642
- const { values, mods, colors } = parseStyle(shadow).groups[0] ?? makeEmptyDetails();
4831
+ function toBoxShadow(group) {
4832
+ const { values, mods, colors } = group;
4643
4833
  const mod = mods[0] || "";
4644
- const shadowColor = (colors && colors[0]) ?? "";
4834
+ const shadowColor = colors[0] ?? "";
4645
4835
  return [
4646
4836
  mod,
4647
4837
  ...values,
@@ -4652,7 +4842,8 @@ function shadowStyle({ shadow }) {
4652
4842
  if (!shadow) return null;
4653
4843
  if (shadow === true) shadow = "var(--shadow)";
4654
4844
  if (CSS_WIDE_KEYWORDS.has(shadow)) return { "box-shadow": shadow };
4655
- return { "box-shadow": shadow.split(",").map(toBoxShadow).join(",") };
4845
+ const { groups } = parseStyle(shadow);
4846
+ return { "box-shadow": groups.map(toBoxShadow).join(",") };
4656
4847
  }
4657
4848
  shadowStyle.__lookupStyles = ["shadow"];
4658
4849
  //#endregion
@@ -12310,6 +12501,6 @@ function resetConfig() {
12310
12501
  delete storage[GLOBAL_INJECTOR_KEY];
12311
12502
  }
12312
12503
  //#endregion
12313
- 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, normalizeDslName as on, 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 };
12314
12505
 
12315
- //# sourceMappingURL=config-DOCuTykY.js.map
12506
+ //# sourceMappingURL=config-CwQ-fAsp.js.map