@tenphi/tasty 2.6.5 → 2.8.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 (43) hide show
  1. package/dist/{collector-c00_hT9R.js → collector-BEF4F_PE.js} +3 -3
  2. package/dist/{collector-c00_hT9R.js.map → collector-BEF4F_PE.js.map} +1 -1
  3. package/dist/{collector-osfWTeRd.d.ts → collector-CpU85p2G.d.ts} +2 -2
  4. package/dist/{config-BoZDUHW5.d.ts → config-3h7BtX4l.d.ts} +2 -2
  5. package/dist/{config-IzenlK2R.js → config-BDnvK42H.js} +308 -74
  6. package/dist/config-BDnvK42H.js.map +1 -0
  7. package/dist/core/index.d.ts +5 -5
  8. package/dist/core/index.js +6 -6
  9. package/dist/{core-J9U8fXzr.js → core-BiFQGi4v.js} +5 -5
  10. package/dist/{core-J9U8fXzr.js.map → core-BiFQGi4v.js.map} +1 -1
  11. package/dist/{css-writer-CCyaR6ZM.js → css-writer-4IMPW4i1.js} +3 -3
  12. package/dist/{css-writer-CCyaR6ZM.js.map → css-writer-4IMPW4i1.js.map} +1 -1
  13. package/dist/{format-rules-CCb7qNPt.js → format-rules-DrpEA0CZ.js} +2 -2
  14. package/dist/{format-rules-CCb7qNPt.js.map → format-rules-DrpEA0CZ.js.map} +1 -1
  15. package/dist/{hydrate-DEsdmcdy.js → hydrate-DItCQmmZ.js} +2 -2
  16. package/dist/{hydrate-DEsdmcdy.js.map → hydrate-DItCQmmZ.js.map} +1 -1
  17. package/dist/{index-tcHuMPFt.d.ts → index-BsJz5xBF.d.ts} +2 -2
  18. package/dist/{index-D-OA_O6i.d.ts → index-C_SRmAWj.d.ts} +193 -163
  19. package/dist/index.d.ts +5 -5
  20. package/dist/index.js +7 -7
  21. package/dist/{keyframes-DxAp6xwG.js → keyframes-DYGxQPjN.js} +2 -2
  22. package/dist/{keyframes-DxAp6xwG.js.map → keyframes-DYGxQPjN.js.map} +1 -1
  23. package/dist/{merge-styles-BMWcH6MF.d.ts → merge-styles-BzOwGOGC.d.ts} +2 -2
  24. package/dist/{merge-styles-Ugifi570.js → merge-styles-oU4CfZY7.js} +2 -2
  25. package/dist/{merge-styles-Ugifi570.js.map → merge-styles-oU4CfZY7.js.map} +1 -1
  26. package/dist/{resolve-recipes-DEmQhiop.js → resolve-recipes-PwM-R6Jc.js} +3 -3
  27. package/dist/{resolve-recipes-DEmQhiop.js.map → resolve-recipes-PwM-R6Jc.js.map} +1 -1
  28. package/dist/ssr/astro-client.js +1 -1
  29. package/dist/ssr/astro.js +3 -3
  30. package/dist/ssr/index.d.ts +1 -1
  31. package/dist/ssr/index.js +3 -3
  32. package/dist/ssr/next.d.ts +1 -1
  33. package/dist/ssr/next.js +4 -4
  34. package/dist/static/index.d.ts +2 -2
  35. package/dist/static/index.js +1 -1
  36. package/dist/zero/babel.d.ts +1 -1
  37. package/dist/zero/babel.js +4 -4
  38. package/dist/zero/index.d.ts +1 -1
  39. package/dist/zero/index.js +1 -1
  40. package/docs/dsl.md +110 -1
  41. package/docs/pipeline.md +40 -2
  42. package/package.json +5 -5
  43. package/dist/config-IzenlK2R.js.map +0 -1
@@ -453,7 +453,19 @@ const K2 = .03;
453
453
  const K3 = (1 + K1) / (1 + K2);
454
454
  const clamp$1 = (value, min, max) => Math.max(Math.min(value, max), min);
455
455
  const constrainAngle = (angle) => (angle % 360 + 360) % 360;
456
+ const toe = (x) => .5 * (K3 * x - K1 + Math.sqrt((K3 * x - K1) * (K3 * x - K1) + 4 * K2 * K3 * x));
456
457
  const toeInv = (x) => (x ** 2 + K1 * x) / (K3 * (x + K2));
458
+ const OKHST_REF_EPS = .05;
459
+ function yToL(y) {
460
+ return toe(Math.cbrt(Math.max(0, y)));
461
+ }
462
+ function yFromTone(t, eps = OKHST_REF_EPS) {
463
+ const den = Math.log(1 + eps) - Math.log(eps);
464
+ return Math.exp(t / 100 * den + Math.log(eps)) - eps;
465
+ }
466
+ function fromTone(t, eps = OKHST_REF_EPS) {
467
+ return yToL(yFromTone(t, eps));
468
+ }
457
469
  const oklabToLinearSrgb = (lab) => {
458
470
  const L = lab[0];
459
471
  const a = lab[1];
@@ -745,6 +757,16 @@ function okhslToSrgb(h, s, l) {
745
757
  ];
746
758
  }
747
759
  /**
760
+ * OKHST to sRGB (0-1 range).
761
+ * @param h - Hue in degrees (0-360)
762
+ * @param s - Saturation (0-1)
763
+ * @param t - Tone (0-1)
764
+ * @returns sRGB values in 0-1 range, clamped to gamut
765
+ */
766
+ function okhstToSrgb(h, s, t) {
767
+ return okhslToSrgb(h, clamp$1(s, 0, 1), clamp$1(fromTone(t * 100), 0, 1));
768
+ }
769
+ /**
748
770
  * OKLCH to sRGB (0-255 range).
749
771
  * @param L - Lightness (0-1)
750
772
  * @param C - Chroma (typically 0-0.4)
@@ -1024,7 +1046,7 @@ function getRgbValuesFromRgbaString(str) {
1024
1046
  }
1025
1047
  /**
1026
1048
  * Convert any recognized color string to an `rgb()` CSS string.
1027
- * Handles hex, `okhsl()`, `hsl()`/`hsla()`, named CSS colors,
1049
+ * Handles hex, `okhsl()`, `okhst()`, `hsl()`/`hsla()`, named CSS colors,
1028
1050
  * and `rgb()`/`rgba()` pass-through.
1029
1051
  */
1030
1052
  function strToRgb(color, _ignoreAlpha = false) {
@@ -1033,6 +1055,7 @@ function strToRgb(color, _ignoreAlpha = false) {
1033
1055
  if (color.startsWith("#")) return hexToRgb(color);
1034
1056
  if (color.startsWith("oklch(")) return oklchStringToRgb(color);
1035
1057
  if (color.startsWith("okhsl(")) return okhslStringToRgb(color);
1058
+ if (color.startsWith("okhst(")) return okhstStringToRgb(color);
1036
1059
  if (color.startsWith("hsl")) return hslStringToRgb(color);
1037
1060
  const namedHex = getNamedColorHex().get(color.toLowerCase());
1038
1061
  if (namedHex) return hexToRgb(namedHex);
@@ -1097,6 +1120,34 @@ function okhslStringToRgb(okhslStr) {
1097
1120
  return `rgb(${r255} ${g255} ${b255})`;
1098
1121
  }
1099
1122
  /**
1123
+ * Convert an `okhst()` color string to an `rgb()`/`rgba()` CSS string.
1124
+ * Supports deg/turn/rad hue units and percentage saturation/tone.
1125
+ */
1126
+ function okhstStringToRgb(okhstStr) {
1127
+ const match = okhstStr.match(/okhst\(([^)]+)\)/i);
1128
+ if (!match) return null;
1129
+ const [colorPart, alphaPart] = match[1].trim().split("/");
1130
+ const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
1131
+ if (parts.length < 3) return null;
1132
+ let h = parseFloat(parts[0]);
1133
+ const hueStr = parts[0].toLowerCase();
1134
+ if (hueStr.endsWith("turn")) h = parseFloat(hueStr) * 360;
1135
+ else if (hueStr.endsWith("rad")) h = parseFloat(hueStr) * 180 / Math.PI;
1136
+ else if (hueStr.endsWith("deg")) h = parseFloat(hueStr);
1137
+ const parsePercent = (val) => {
1138
+ const num = parseFloat(val);
1139
+ return val.includes("%") ? num / 100 : num;
1140
+ };
1141
+ const s = Math.max(0, Math.min(1, parsePercent(parts[1])));
1142
+ const t = Math.max(0, Math.min(1, parsePercent(parts[2])));
1143
+ const [r, g, b] = okhstToSrgb(h, s, t);
1144
+ const r255 = Math.round(Math.max(0, Math.min(1, r)) * 255);
1145
+ const g255 = Math.round(Math.max(0, Math.min(1, g)) * 255);
1146
+ const b255 = Math.round(Math.max(0, Math.min(1, b)) * 255);
1147
+ if (alphaPart) return `rgba(${r255}, ${g255}, ${b255}, ${parseFloat(alphaPart.trim())})`;
1148
+ return `rgb(${r255} ${g255} ${b255})`;
1149
+ }
1150
+ /**
1100
1151
  * Convert an `oklch()` color string to an `rgb()`/`rgba()` CSS string.
1101
1152
  * Supports deg/turn/rad hue units and percentage lightness.
1102
1153
  */
@@ -1269,6 +1320,17 @@ function resolveToRgbaValues(color) {
1269
1320
  parsed.alpha
1270
1321
  ];
1271
1322
  }
1323
+ if (trimmed.startsWith("okhst(")) {
1324
+ const parsed = parseColorFuncArgs(trimmed, "okhst");
1325
+ if (!parsed) return null;
1326
+ const [r, g, b] = okhstToSrgb(parseHue(parsed.parts[0]), clamp01(parsePercent(parsed.parts[1])), clamp01(parsePercent(parsed.parts[2])));
1327
+ return [
1328
+ clamp01(r) * 255,
1329
+ clamp01(g) * 255,
1330
+ clamp01(b) * 255,
1331
+ parsed.alpha
1332
+ ];
1333
+ }
1272
1334
  const fallback = strToRgb(trimmed);
1273
1335
  if (fallback) {
1274
1336
  if (fallback !== trimmed) return resolveToRgbaValues(fallback);
@@ -1602,7 +1664,7 @@ function classify(raw, opts, recurse) {
1602
1664
  if (baseToken in predefinedTokens) {
1603
1665
  const resolvedValue = predefinedTokens[baseToken];
1604
1666
  if (resolvedValue.startsWith("#")) return classify(`${resolvedValue.toLowerCase()}.${rawAlpha}`, opts, recurse);
1605
- const funcMatch = resolvedValue.match(/^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|device-cmyk|gray|color-mix|color-contrast)\((.+)\)$/i);
1667
+ const funcMatch = resolvedValue.match(/^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|okhst|device-cmyk|gray|color-mix|color-contrast)\((.+)\)$/i);
1606
1668
  if (funcMatch) {
1607
1669
  const [, funcName, args] = funcMatch;
1608
1670
  let alpha;
@@ -1983,20 +2045,7 @@ var StyleParser = class {
1983
2045
  }
1984
2046
  };
1985
2047
  //#endregion
1986
- //#region src/plugins/okhsl-plugin.ts
1987
- /**
1988
- * OKHSL Plugin for Tasty
1989
- *
1990
- * Converts OKHSL color syntax to RGB notation.
1991
- * Supports angle units: deg, turn, rad, or unitless (degrees).
1992
- *
1993
- * Examples:
1994
- * okhsl(240.5 50% 50%)
1995
- * okhsl(240.5deg 50% 50%)
1996
- * okhsl(0.25turn 50% 50%)
1997
- * okhsl(1.57rad 50% 50%)
1998
- */
1999
- const conversionCache = new Lru(500);
2048
+ //#region src/plugins/color-func.ts
2000
2049
  const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
2001
2050
  /**
2002
2051
  * Parse an angle value with optional unit.
@@ -2022,32 +2071,56 @@ const parsePercentage = (value) => {
2022
2071
  return value.includes("%") ? num / 100 : num;
2023
2072
  };
2024
2073
  /**
2074
+ * Creates a color function handler for the tasty parser.
2075
+ * @param name The name of the color space (e.g., 'okhsl', 'okhst')
2076
+ * @param channelLabel The label for the channels in warnings (e.g., 'H S L')
2077
+ * @param convert A function that converts the parsed H, C2, C3 values to sRGB [r, g, b] (0-1)
2078
+ */
2079
+ function createColorFunc(name, channelLabel, convert) {
2080
+ const conversionCache = new Lru(500);
2081
+ return (groups) => {
2082
+ if (groups.length === 0 || groups[0].all.length < 3) {
2083
+ console.warn(`[${name}] Expected 3 values (${channelLabel}), got:`, groups);
2084
+ return "rgb(0% 0% 0%)";
2085
+ }
2086
+ const group = groups[0];
2087
+ const tokens = group.all;
2088
+ const alpha = group.parts.length > 1 && group.parts[1].all.length > 0 ? group.parts[1].output : void 0;
2089
+ const cacheKey = tokens.slice(0, 3).join(" ") + (alpha ? ` / ${alpha}` : "");
2090
+ const cached = conversionCache.get(cacheKey);
2091
+ if (cached) return cached;
2092
+ const h = parseAngle(tokens[0]);
2093
+ const c2 = parsePercentage(tokens[1]);
2094
+ const c3 = parsePercentage(tokens[2]);
2095
+ const [r, g, b] = convert(h, clamp(c2, 0, 1), clamp(c3, 0, 1));
2096
+ const format = (n) => {
2097
+ const pct = n * 100;
2098
+ return parseFloat(pct.toFixed(1)).toString() + "%";
2099
+ };
2100
+ const result = alpha ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})` : `rgb(${format(r)} ${format(g)} ${format(b)})`;
2101
+ conversionCache.set(cacheKey, result);
2102
+ return result;
2103
+ };
2104
+ }
2105
+ //#endregion
2106
+ //#region src/plugins/okhsl-plugin.ts
2107
+ /**
2108
+ * OKHSL Plugin for Tasty
2109
+ *
2110
+ * Converts OKHSL color syntax to RGB notation.
2111
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
2112
+ *
2113
+ * Examples:
2114
+ * okhsl(240.5 50% 50%)
2115
+ * okhsl(240.5deg 50% 50%)
2116
+ * okhsl(0.25turn 50% 50%)
2117
+ * okhsl(1.57rad 50% 50%)
2118
+ */
2119
+ /**
2025
2120
  * The okhsl function handler for tasty parser.
2026
2121
  * Receives parsed style groups and returns an RGB color string.
2027
2122
  */
2028
- const okhslFunc = (groups) => {
2029
- if (groups.length === 0 || groups[0].all.length < 3) {
2030
- console.warn("[okhsl] Expected 3 values (H S L), got:", groups);
2031
- return "rgb(0% 0% 0%)";
2032
- }
2033
- const group = groups[0];
2034
- const tokens = group.all;
2035
- const alpha = group.parts.length > 1 && group.parts[1].all.length > 0 ? group.parts[1].output : void 0;
2036
- const cacheKey = tokens.slice(0, 3).join(" ") + (alpha ? ` / ${alpha}` : "");
2037
- const cached = conversionCache.get(cacheKey);
2038
- if (cached) return cached;
2039
- const h = parseAngle(tokens[0]);
2040
- const s = parsePercentage(tokens[1]);
2041
- const l = parsePercentage(tokens[2]);
2042
- const [r, g, b] = okhslToSrgb(h, clamp(s, 0, 1), clamp(l, 0, 1));
2043
- const format = (n) => {
2044
- const pct = n * 100;
2045
- return parseFloat(pct.toFixed(1)).toString() + "%";
2046
- };
2047
- const result = alpha ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})` : `rgb(${format(r)} ${format(g)} ${format(b)})`;
2048
- conversionCache.set(cacheKey, result);
2049
- return result;
2050
- };
2123
+ const okhslFunc = createColorFunc("okhsl", "H S L", okhslToSrgb);
2051
2124
  /**
2052
2125
  * OKHSL Plugin for Tasty.
2053
2126
  *
@@ -2075,6 +2148,52 @@ const okhslPlugin = () => ({
2075
2148
  funcs: { okhsl: okhslFunc }
2076
2149
  });
2077
2150
  //#endregion
2151
+ //#region src/plugins/okhst-plugin.ts
2152
+ /**
2153
+ * OKHST Plugin for Tasty
2154
+ *
2155
+ * Converts OKHST color syntax to RGB notation.
2156
+ * OKHST is OKHSL with the lightness axis replaced by a contrast-uniform tone axis.
2157
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
2158
+ *
2159
+ * Examples:
2160
+ * okhst(240.5 50% 50%)
2161
+ * okhst(240.5deg 50% 50%)
2162
+ * okhst(0.25turn 50% 50%)
2163
+ * okhst(1.57rad 50% 50%)
2164
+ */
2165
+ /**
2166
+ * The okhst function handler for tasty parser.
2167
+ * Receives parsed style groups and returns an RGB color string.
2168
+ */
2169
+ const okhstFunc = createColorFunc("okhst", "H S T", okhstToSrgb);
2170
+ /**
2171
+ * OKHST Plugin for Tasty.
2172
+ *
2173
+ * Adds support for the `okhst()` color function in tasty styles.
2174
+ *
2175
+ * @example
2176
+ * ```ts
2177
+ * import { configure } from '@tenphi/tasty';
2178
+ * import { okhstPlugin } from '@tenphi/tasty';
2179
+ *
2180
+ * configure({
2181
+ * plugins: [okhstPlugin()],
2182
+ * });
2183
+ *
2184
+ * // Now you can use okhst in styles:
2185
+ * const Box = tasty({
2186
+ * styles: {
2187
+ * fill: 'okhst(240 50% 50%)',
2188
+ * },
2189
+ * });
2190
+ * ```
2191
+ */
2192
+ const okhstPlugin = () => ({
2193
+ name: "okhst",
2194
+ funcs: { okhst: okhstFunc }
2195
+ });
2196
+ //#endregion
2078
2197
  //#region src/utils/styles.ts
2079
2198
  /**
2080
2199
  * Normalize a color token value.
@@ -2101,7 +2220,7 @@ function isSimpleColorFast(val) {
2101
2220
  case 114: return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98;
2102
2221
  case 104: return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108;
2103
2222
  case 108: return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104;
2104
- case 111: return val.startsWith("oklch(") || val.startsWith("okhsl(");
2223
+ case 111: return val.startsWith("oklch(") || val.startsWith("okhsl(") || val.startsWith("okhst(");
2105
2224
  case 118: return RE_VAR_COLOR.test(val);
2106
2225
  case 99: return val === "currentColor" || val === "currentcolor";
2107
2226
  case 116: return val === "transparent";
@@ -2134,7 +2253,10 @@ function getOrCreateParser() {
2134
2253
  }
2135
2254
  return __tastyParser;
2136
2255
  }
2137
- const __tastyFuncs = { okhsl: okhslFunc };
2256
+ const __tastyFuncs = {
2257
+ okhsl: okhslFunc,
2258
+ okhst: okhstFunc
2259
+ };
2138
2260
  function customFunc(name, fn) {
2139
2261
  __tastyFuncs[name] = fn;
2140
2262
  getOrCreateParser().setFuncs(__tastyFuncs);
@@ -7442,6 +7564,18 @@ function wrapInIsOrNot(args, negated) {
7442
7564
  return `${negated ? ":not" : ":is"}(${args.sort().join(", ")})`;
7443
7565
  }
7444
7566
  /**
7567
+ * Wrap a non-empty selector fragment in `:where(...)` to zero its
7568
+ * specificity. Tasty anchors specificity solely on the doubled class
7569
+ * (`.tXX.tXX`) and `data-element` attributes; every stateful selector
7570
+ * (modifiers, pseudos, :is()/:not() groups, root/parent context) is wrapped
7571
+ * so it contributes nothing to specificity and the cascade is decided by
7572
+ * source order alone. Empty input passes through unchanged.
7573
+ */
7574
+ function wrapWhere(inner) {
7575
+ if (!inner) return "";
7576
+ return `:where(${inner})`;
7577
+ }
7578
+ /**
7445
7579
  * Convert a selector group to a CSS selector fragment.
7446
7580
  *
7447
7581
  * Single-branch groups are unwrapped (no :is() wrapper).
@@ -7545,7 +7679,7 @@ function rootGroupsToCSS(groups) {
7545
7679
  if (optimized.length === 0) return void 0;
7546
7680
  let prefix = ":root";
7547
7681
  for (const group of optimized) prefix += selectorGroupToCSS(group);
7548
- return prefix;
7682
+ return wrapWhere(prefix);
7549
7683
  }
7550
7684
  /**
7551
7685
  * Convert parent groups to CSS selector fragments (for final output).
@@ -7557,7 +7691,8 @@ function parentGroupsToCSS(groups) {
7557
7691
  for (const group of groups) {
7558
7692
  const combinator = group.direct ? " > *" : " *";
7559
7693
  const args = group.branches.map((branch) => branchToCSS(branch) + combinator);
7560
- result += wrapInIsOrNot(args, group.negated);
7694
+ const inner = group.negated ? `:not(${args.sort().join(", ")})` : args.sort().join(", ");
7695
+ result += wrapWhere(inner);
7561
7696
  }
7562
7697
  return result;
7563
7698
  }
@@ -8227,6 +8362,21 @@ function buildAtRulesFromVariant(variant) {
8227
8362
  return atRules;
8228
8363
  }
8229
8364
  //#endregion
8365
+ //#region src/pipeline/warnings.ts
8366
+ const defaultWarningHandler = (warning) => {
8367
+ console.warn(`[Tasty] ${warning.message}`);
8368
+ };
8369
+ let warningHandler = defaultWarningHandler;
8370
+ /**
8371
+ * Emit a structured pipeline warning via the configured handler.
8372
+ */
8373
+ function emitWarning(code, message) {
8374
+ warningHandler({
8375
+ code,
8376
+ message
8377
+ });
8378
+ }
8379
+ //#endregion
8230
8380
  //#region src/pipeline/exclusive.ts
8231
8381
  /**
8232
8382
  * Build exclusive conditions for a list of parsed style entries.
@@ -8255,7 +8405,12 @@ function buildAtRulesFromVariant(variant) {
8255
8405
  function buildExclusiveConditions(entries) {
8256
8406
  const result = [];
8257
8407
  const priorConditions = [];
8408
+ const floors = [];
8258
8409
  for (const entry of entries) {
8410
+ if (entry.floor) {
8411
+ floors.push(entry);
8412
+ continue;
8413
+ }
8259
8414
  let exclusive = entry.condition;
8260
8415
  for (const prior of priorConditions) {
8261
8416
  if (prior.kind === "true") continue;
@@ -8270,6 +8425,10 @@ function buildExclusiveConditions(entries) {
8270
8425
  });
8271
8426
  if (entry.condition.kind !== "true") priorConditions.push(entry.condition);
8272
8427
  }
8428
+ for (const floor of floors) result.push({
8429
+ ...floor,
8430
+ exclusiveCondition: floor.condition
8431
+ });
8273
8432
  return result;
8274
8433
  }
8275
8434
  /**
@@ -8282,19 +8441,85 @@ function buildExclusiveConditions(entries) {
8282
8441
  */
8283
8442
  function parseStyleEntries(styleKey, valueMap, parseCondition) {
8284
8443
  const entries = [];
8285
- Object.keys(valueMap).forEach((stateKey, index) => {
8444
+ let floorEntry = null;
8445
+ Object.keys(valueMap).forEach((stateKey) => {
8286
8446
  const value = valueMap[stateKey];
8447
+ if (stateKey === "_") {
8448
+ floorEntry = {
8449
+ styleKey,
8450
+ stateKey,
8451
+ value,
8452
+ condition: trueCondition(),
8453
+ priority: 0,
8454
+ floor: true
8455
+ };
8456
+ return;
8457
+ }
8458
+ if (isMisusedFallbackKey(stateKey)) {
8459
+ emitWarning("INVALID_FALLBACK_KEY", `Style key "${stateKey}" (in "${styleKey}") combines the fallback "_" with other state logic. "_" can only be used on its own as a map-wide fallback floor. The key has been ignored.`);
8460
+ return;
8461
+ }
8287
8462
  const condition = stateKey === "" ? trueCondition() : parseCondition(stateKey);
8288
8463
  entries.push({
8289
8464
  styleKey,
8290
8465
  stateKey,
8291
8466
  value,
8292
8467
  condition,
8293
- priority: index
8468
+ priority: 0
8294
8469
  });
8295
8470
  });
8296
- entries.reverse();
8297
- return entries;
8471
+ if (floorEntry !== null) {
8472
+ const hasOtherStates = entries.some((e) => e.condition.kind !== "true");
8473
+ const hasDefault = entries.some((e) => e.condition.kind === "true");
8474
+ if (!hasOtherStates && hasDefault) {
8475
+ emitWarning("REDUNDANT_DEFAULT_STATE", `Style "${styleKey}" defines both a "_" fallback and a "" default with no other states. The "" default is redundant (always superseded by "_"); the "_" value is used and the "" default is ignored.`);
8476
+ entries.length = 0;
8477
+ }
8478
+ }
8479
+ const normalized = normalizeDefaultStates(entries, styleKey);
8480
+ const ordered = floorEntry !== null ? [floorEntry, ...normalized] : normalized;
8481
+ ordered.forEach((entry, index) => {
8482
+ entry.priority = index;
8483
+ });
8484
+ ordered.reverse();
8485
+ return ordered;
8486
+ }
8487
+ /**
8488
+ * Detect a misused standalone `_` fallback key: a key that is not exactly
8489
+ * `_` but contains `_` as a standalone atom among state-logic operators
8490
+ * (`&`, `|`, `^`, `,`) or grouping parens. A `_` embedded in a longer token
8491
+ * (e.g. a modifier named `_private`) is not a misuse.
8492
+ */
8493
+ function isMisusedFallbackKey(stateKey) {
8494
+ if (stateKey === "_" || !stateKey.includes("_")) return false;
8495
+ return /(?:^|[\s&|^,(])_(?:[\s&|^,)]|$)/.test(stateKey);
8496
+ }
8497
+ /**
8498
+ * Resolve the bare `''` default state (`condition.kind === 'true'`) in a
8499
+ * style value map.
8500
+ *
8501
+ * The bare default only behaves correctly as the **first** (lowest-priority)
8502
+ * key. When it appears later, `buildExclusiveConditions` processes it first
8503
+ * (after the reverse), never adds it to the prior list, and never negates it
8504
+ * — so it silently overrides every state authored before it.
8505
+ *
8506
+ * This function, given entries in authored order, moves a misplaced bare
8507
+ * default to the front (lowest priority), preserving the relative order of
8508
+ * all other entries, and emits a `MISPLACED_DEFAULT_STATE` warning.
8509
+ *
8510
+ * The `_` fallback floor is pulled out before this runs (see
8511
+ * `parseStyleEntries`), so only the bare `''` default is considered here.
8512
+ */
8513
+ function normalizeDefaultStates(entries, styleKey) {
8514
+ const defaultPos = entries.findIndex((e) => e.condition.kind === "true");
8515
+ if (defaultPos <= 0) return entries;
8516
+ const defaultEntry = entries[defaultPos];
8517
+ emitWarning("MISPLACED_DEFAULT_STATE", `Style "${styleKey}" defines the default state "${defaultEntry.stateKey}" after other states. A bare default ("") must be the first key, otherwise it overrides the states above it. It has been moved to the first position. Define default states first.`);
8518
+ return [
8519
+ defaultEntry,
8520
+ ...entries.slice(0, defaultPos),
8521
+ ...entries.slice(defaultPos + 1)
8522
+ ];
8298
8523
  }
8299
8524
  /**
8300
8525
  * Merge parsed entries that share the same value.
@@ -8417,8 +8642,9 @@ function extractCompoundStates(valueMap) {
8417
8642
  const keys = Object.keys(valueMap);
8418
8643
  if (keys.length < 3 || !keys.some((k) => k.includes("&"))) return valueMap;
8419
8644
  const entries = keys.map((key) => {
8645
+ const atoms = splitTopLevelAnd(key);
8420
8646
  return {
8421
- atoms: splitTopLevelAnd(key) ?? [key],
8647
+ atoms: atoms === null || atoms.includes("_") ? [key] : atoms,
8422
8648
  value: valueMap[key]
8423
8649
  };
8424
8650
  });
@@ -8723,21 +8949,6 @@ function transformSelectorContent(content) {
8723
8949
  return content.replace(ELEMENT_NAME_RE, (_, prefix, name) => `${prefix}[data-element="${name}"]`);
8724
8950
  }
8725
8951
  //#endregion
8726
- //#region src/pipeline/warnings.ts
8727
- const defaultWarningHandler = (warning) => {
8728
- console.warn(`[Tasty] ${warning.message}`);
8729
- };
8730
- let warningHandler = defaultWarningHandler;
8731
- /**
8732
- * Emit a structured pipeline warning via the configured handler.
8733
- */
8734
- function emitWarning(code, message) {
8735
- warningHandler({
8736
- code,
8737
- message
8738
- });
8739
- }
8740
- //#endregion
8741
8952
  //#region src/pipeline/parseStateKey.ts
8742
8953
  /**
8743
8954
  * State Key Parser
@@ -9252,7 +9463,17 @@ function runPipeline(styles, parserContext) {
9252
9463
  const starting = [];
9253
9464
  for (const rule of dedupedRules) if (rule.startingStyle) starting.push(rule);
9254
9465
  else normal.push(rule);
9255
- return normal.concat(starting);
9466
+ return stableSortByOrder(normal).concat(stableSortByOrder(starting));
9467
+ }
9468
+ /**
9469
+ * Stable sort CSS rules by their `order` hint ascending. Rules without an
9470
+ * `order` are treated as 0. `Array.prototype.sort` is stable (ES2019+,
9471
+ * Node >= 20), so equal-order rules keep their emission order — the `_`
9472
+ * fallback floor (low order) stays before the overrides that layer over it.
9473
+ */
9474
+ function stableSortByOrder(rules) {
9475
+ if (rules.length <= 1) return rules;
9476
+ return [...rules].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
9256
9477
  }
9257
9478
  /**
9258
9479
  * Process styles at a given nesting level.
@@ -9360,7 +9581,8 @@ function invokeHandler(handler, stateSnapshots, selectorSuffix) {
9360
9581
  for (const suffix of suffixes) computedRules.push({
9361
9582
  condition: snapshot.condition,
9362
9583
  declarations,
9363
- selectorSuffix: suffix
9584
+ selectorSuffix: suffix,
9585
+ order: snapshot.order
9364
9586
  });
9365
9587
  }
9366
9588
  }
@@ -9778,10 +10000,15 @@ function computeStateCombinations(exclusiveByStyle, lookupStyles) {
9778
10000
  const simplified = simplifyCondition(and(...combo.map((e) => e.exclusiveCondition)));
9779
10001
  if (simplified.kind === "false") continue;
9780
10002
  const values = {};
9781
- for (const entry of combo) values[entry.styleKey] = entry.value;
10003
+ let order = 0;
10004
+ for (const entry of combo) {
10005
+ values[entry.styleKey] = entry.value;
10006
+ if (entry.priority > order) order = entry.priority;
10007
+ }
9782
10008
  snapshots.push({
9783
10009
  condition: simplified,
9784
- values
10010
+ values,
10011
+ order
9785
10012
  });
9786
10013
  }
9787
10014
  return snapshots;
@@ -9829,10 +10056,13 @@ function mergeByValue(rules) {
9829
10056
  for (const [, groupRules] of groups) if (groupRules.length === 1) merged.push(groupRules[0]);
9830
10057
  else {
9831
10058
  const mergedCondition = simplifyCondition(or(...groupRules.map((r) => r.condition)));
10059
+ let order = groupRules[0].order;
10060
+ for (const r of groupRules) if (r.order < order) order = r.order;
9832
10061
  merged.push({
9833
10062
  condition: mergedCondition,
9834
10063
  declarations: groupRules[0].declarations,
9835
- selectorSuffix: groupRules[0].selectorSuffix
10064
+ selectorSuffix: groupRules[0].selectorSuffix,
10065
+ order
9836
10066
  });
9837
10067
  }
9838
10068
  return merged;
@@ -9842,12 +10072,15 @@ function mergeByValue(rules) {
9842
10072
  */
9843
10073
  function buildSelectorFromVariant(variant, selectorSuffix) {
9844
10074
  let selector = "";
9845
- selector += branchToCSS([...variant.modifierConditions, ...variant.pseudoConditions]);
9846
- for (const group of variant.selectorGroups) selector += selectorGroupToCSS(group);
10075
+ let rootSegment = branchToCSS([...variant.modifierConditions, ...variant.pseudoConditions]);
10076
+ for (const group of variant.selectorGroups) rootSegment += selectorGroupToCSS(group);
10077
+ selector += wrapWhere(rootSegment);
9847
10078
  if (variant.parentGroups.length > 0) selector += parentGroupsToCSS(variant.parentGroups);
9848
10079
  selector += selectorSuffix;
9849
10080
  const ownOptimized = optimizeGroups(variant.ownGroups);
9850
- for (const group of ownOptimized) selector += selectorGroupToCSS(group);
10081
+ let ownSegment = "";
10082
+ for (const group of ownOptimized) ownSegment += selectorGroupToCSS(group);
10083
+ selector += wrapWhere(ownSegment);
9851
10084
  return selector;
9852
10085
  }
9853
10086
  /**
@@ -9882,7 +10115,8 @@ function materializeComputedRule(rule) {
9882
10115
  const selectorFragments = mergeVariantsIntoSelectorGroups(group.variants).map((v) => buildSelectorFromVariant(v, rule.selectorSuffix));
9883
10116
  const cssRule = {
9884
10117
  selector: selectorFragments.length === 1 ? selectorFragments[0] : selectorFragments,
9885
- declarations
10118
+ declarations,
10119
+ order: rule.order
9886
10120
  };
9887
10121
  if (group.atRules.length > 0) cssRule.atRules = group.atRules;
9888
10122
  if (group.rootPrefix) cssRule.rootPrefix = group.rootPrefix;
@@ -10594,6 +10828,6 @@ function resetConfig() {
10594
10828
  delete storage[GLOBAL_INJECTOR_KEY];
10595
10829
  }
10596
10830
  //#endregion
10597
- export { parseColor as $, StyleInjector as A, hasLocalProperties as At, styleHandlers as B, parseStateKey as C, getRgbValuesFromRgbaString as Ct, extractPredefinedStateRefs as D, Lru as Dt, extractLocalPredefinedStates as E, strToRgb as Et, fontFaceContentHash as F, CUSTOM_UNITS as G, warn as H, formatFontFaceRule as I, filterMods as J, DIRECTIONS as K, hasLocalFontFace as L, formatCounterStyleRule as M, hasLocalCounterStyle as N, getGlobalPredefinedStates as O, extractLocalProperties as Ot, extractLocalFontFace as P, normalizeColorTokenValue as Q, SheetManager as R, renderStyles as S, getNamedColorHex as St, createStateParserContext as T, hslToRgbValues as Tt, createStyle as U, deprecationWarning as V, PropertyTypeResolver as W, getGlobalParser as X, getGlobalFuncs as Y, getGlobalPredefinedTokens as Z, markStylesGenerated as _, colorInitialValueToComponents as _t, getGlobalCounterStyle as a, okhslPlugin as at, hasPipelineCacheEntry as b, getColorSpaceSuffix as bt, getGlobalKeyframes as c, DEFAULT_NAME_PREFIX as ct, getNamePrefix as d, makeCounterStyleName as dt, parseStyle as et, hasGlobalKeyframes as f, makeKeyframeName as ft, isTestEnvironment as g, hashString as gt, isConfigLocked as h, isDevEnv as ht, getGlobalConfigTokens as i, okhslFunc as it, extractLocalCounterStyle as j, parsePropertyToken as jt, setGlobalPredefinedStates as k, getEffectiveDefinition as kt, getGlobalRecipes as l, DEFAULT_ZERO_NAME_PREFIX as lt, hasStylesGenerated as m, validateNamePrefix as mt, getConfig as n, setGlobalPredefinedTokens as nt, getGlobalFontFace as o, StyleParser as ot, hasGlobalRecipes as p, tastyClassRegex as pt, customFunc as q, getEffectiveProperties as r, stringifyStyles as rt, getGlobalInjector as s, Bucket as st, configure as t, resetGlobalPredefinedTokens as tt, getGlobalStyles as u, makeClassName as ut, resetConfig as v, getColorSpaceComponents as vt, camelToKebab as w, hexToRgb as wt, isSelector as x, getComponentPropertySyntax as xt, generateTypographyTokens as y, getColorSpaceFunc as yt, STYLE_HANDLER_MAP as z };
10831
+ export { parseColor as $, StyleInjector as A, extractLocalProperties as At, styleHandlers as B, parseStateKey as C, getComponentPropertySyntax as Ct, extractPredefinedStateRefs as D, hslToRgbValues as Dt, extractLocalPredefinedStates as E, hexToRgb as Et, fontFaceContentHash as F, CUSTOM_UNITS as G, warn as H, formatFontFaceRule as I, filterMods as J, DIRECTIONS as K, hasLocalFontFace as L, formatCounterStyleRule as M, hasLocalProperties as Mt, hasLocalCounterStyle as N, parsePropertyToken as Nt, getGlobalPredefinedStates as O, strToRgb as Ot, extractLocalFontFace as P, normalizeColorTokenValue as Q, SheetManager as R, renderStyles as S, getColorSpaceSuffix as St, createStateParserContext as T, getRgbValuesFromRgbaString as Tt, createStyle as U, deprecationWarning as V, PropertyTypeResolver as W, getGlobalParser as X, getGlobalFuncs as Y, getGlobalPredefinedTokens as Z, markStylesGenerated as _, isDevEnv as _t, getGlobalCounterStyle as a, okhstPlugin as at, hasPipelineCacheEntry as b, getColorSpaceComponents as bt, getGlobalKeyframes as c, StyleParser as ct, getNamePrefix as d, DEFAULT_ZERO_NAME_PREFIX as dt, parseStyle as et, hasGlobalKeyframes as f, makeClassName as ft, isTestEnvironment as g, validateNamePrefix as gt, isConfigLocked as h, tastyClassRegex as ht, getGlobalConfigTokens as i, okhstFunc as it, extractLocalCounterStyle as j, getEffectiveDefinition as jt, setGlobalPredefinedStates as k, Lru as kt, getGlobalRecipes as l, Bucket as lt, hasStylesGenerated as m, makeKeyframeName as mt, getConfig as n, setGlobalPredefinedTokens as nt, getGlobalFontFace as o, okhslFunc as ot, hasGlobalRecipes as p, makeCounterStyleName as pt, customFunc as q, getEffectiveProperties as r, stringifyStyles as rt, getGlobalInjector as s, okhslPlugin as st, configure as t, resetGlobalPredefinedTokens as tt, getGlobalStyles as u, DEFAULT_NAME_PREFIX as ut, resetConfig as v, hashString as vt, camelToKebab as w, getNamedColorHex as wt, isSelector as x, getColorSpaceFunc as xt, generateTypographyTokens as y, colorInitialValueToComponents as yt, STYLE_HANDLER_MAP as z };
10598
10832
 
10599
- //# sourceMappingURL=config-IzenlK2R.js.map
10833
+ //# sourceMappingURL=config-BDnvK42H.js.map