@tenphi/tasty 2.11.1 → 3.0.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 (76) hide show
  1. package/README.md +32 -7
  2. package/dist/astro-B53trJyX.js +156 -0
  3. package/dist/astro-B53trJyX.js.map +1 -0
  4. package/dist/babel-HlEpSZrj.d.ts +83 -0
  5. package/dist/{collector-DYA5AOwr.d.ts → collector-BPzZezAG.d.ts} +44 -7
  6. package/dist/{collector-AHZaBSv8.js → collector-D14DcZx9.js} +75 -14
  7. package/dist/collector-D14DcZx9.js.map +1 -0
  8. package/dist/{config-YDAcLaVf.js → config-CCcE_tqx.js} +2312 -1131
  9. package/dist/config-CCcE_tqx.js.map +1 -0
  10. package/dist/{config-BtK9fUaz.d.ts → config-D2INrjr_.d.ts} +252 -25
  11. package/dist/core/index.d.ts +5 -5
  12. package/dist/core/index.js +6 -6
  13. package/dist/{core-CS4bzqGu.js → core-B5d0IPi2.js} +85 -133
  14. package/dist/core-B5d0IPi2.js.map +1 -0
  15. package/dist/{css-writer-BXSANVrq.js → css-writer-B5DBoxhf.js} +54 -15
  16. package/dist/css-writer-B5DBoxhf.js.map +1 -0
  17. package/dist/{format-rules-CYriCDwq.js → format-rules-BbKDjsfO.js} +2 -2
  18. package/dist/{format-rules-CYriCDwq.js.map → format-rules-BbKDjsfO.js.map} +1 -1
  19. package/dist/{hydrate-GVTorHpU.js → hydrate-CwALW3J9.js} +3 -11
  20. package/dist/{hydrate-GVTorHpU.js.map → hydrate-CwALW3J9.js.map} +1 -1
  21. package/dist/{index-BYtnj_gA.d.ts → index-BAAbdfRs.d.ts} +228 -153
  22. package/dist/{index-B9ih23uv.d.ts → index-l_NdJSUS.d.ts} +172 -61
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +191 -56
  25. package/dist/index.js.map +1 -1
  26. package/dist/{keyframes-CtcSlw4k.js → keyframes-B8M1FmtE.js} +10 -181
  27. package/dist/keyframes-B8M1FmtE.js.map +1 -0
  28. package/dist/{merge-styles-D4ITH4bc.js → merge-styles--1zOSFlS.js} +2 -2
  29. package/dist/{merge-styles-D4ITH4bc.js.map → merge-styles--1zOSFlS.js.map} +1 -1
  30. package/dist/{merge-styles-BZV-XAwX.d.ts → merge-styles-NEvqayL_.d.ts} +2 -2
  31. package/dist/{resolve-recipes-llr0COs8.js → resolve-recipes-Caix5NnH.js} +3 -3
  32. package/dist/{resolve-recipes-llr0COs8.js.map → resolve-recipes-Caix5NnH.js.map} +1 -1
  33. package/dist/ssr/astro-client.js +1 -1
  34. package/dist/ssr/astro-middleware.js +1 -1
  35. package/dist/ssr/astro-middleware.js.map +1 -1
  36. package/dist/ssr/astro.d.ts +1 -5
  37. package/dist/ssr/astro.js +2 -147
  38. package/dist/ssr/index.d.ts +2 -9
  39. package/dist/ssr/index.js +4 -4
  40. package/dist/ssr/index.js.map +1 -1
  41. package/dist/ssr/next.d.ts +1 -1
  42. package/dist/ssr/next.js +5 -6
  43. package/dist/ssr/next.js.map +1 -1
  44. package/dist/static/index.d.ts +2 -2
  45. package/dist/static/index.js +1 -1
  46. package/dist/zero/babel.d.ts +2 -80
  47. package/dist/zero/babel.js +27 -23
  48. package/dist/zero/babel.js.map +1 -1
  49. package/dist/zero/index.d.ts +16 -2
  50. package/dist/zero/index.js +2 -2
  51. package/dist/zero/next.d.ts +1 -1
  52. package/dist/zero/next.js +1 -1
  53. package/dist/zero/next.js.map +1 -1
  54. package/docs/README.md +3 -1
  55. package/docs/ai-agents.md +21 -7
  56. package/docs/configuration.md +220 -30
  57. package/docs/debug.md +7 -7
  58. package/docs/design-system.md +2 -2
  59. package/docs/dsl.md +68 -8
  60. package/docs/injector.md +46 -4
  61. package/docs/methodology.md +1 -1
  62. package/docs/migration-v3.md +285 -0
  63. package/docs/pipeline.md +3 -3
  64. package/docs/plugins.md +349 -0
  65. package/docs/react-api.md +82 -3
  66. package/docs/ssr.md +40 -33
  67. package/docs/styles.md +27 -9
  68. package/docs/tasty-static.md +8 -6
  69. package/package.json +7 -7
  70. package/tasty.config.ts +4 -0
  71. package/dist/collector-AHZaBSv8.js.map +0 -1
  72. package/dist/config-YDAcLaVf.js.map +0 -1
  73. package/dist/core-CS4bzqGu.js.map +0 -1
  74. package/dist/css-writer-BXSANVrq.js.map +0 -1
  75. package/dist/keyframes-CtcSlw4k.js.map +0 -1
  76. package/dist/ssr/astro.js.map +0 -1
@@ -1,277 +1,3 @@
1
- //#region src/parser/const.ts
2
- const VALUE_KEYWORDS = new Set([
3
- "auto",
4
- "max-content",
5
- "min-content",
6
- "fit-content",
7
- "stretch",
8
- "initial",
9
- "inherit",
10
- "revert",
11
- "unset",
12
- "revert-layer"
13
- ]);
14
- const CSS_WIDE_KEYWORDS = new Set([
15
- "initial",
16
- "inherit",
17
- "revert",
18
- "unset",
19
- "revert-layer"
20
- ]);
21
- const COLOR_FUNCS = new Set([
22
- "rgb",
23
- "rgba",
24
- "hsl",
25
- "hsla",
26
- "hwb",
27
- "lab",
28
- "lch",
29
- "oklab",
30
- "oklch",
31
- "color",
32
- "device-cmyk",
33
- "gray",
34
- "color-mix",
35
- "color-contrast"
36
- ]);
37
- const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
38
- const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
39
- const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
40
- const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
41
- const CANONICAL_FUNC_CASE = new Map([
42
- ["translatex", "translateX"],
43
- ["translatey", "translateY"],
44
- ["translatez", "translateZ"],
45
- ["scalex", "scaleX"],
46
- ["scaley", "scaleY"],
47
- ["scalez", "scaleZ"],
48
- ["rotatex", "rotateX"],
49
- ["rotatey", "rotateY"],
50
- ["rotatez", "rotateZ"],
51
- ["skewx", "skewX"],
52
- ["skewy", "skewY"]
53
- ]);
54
- function canonicalFuncName(lowered) {
55
- return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
56
- }
57
- //#endregion
58
- //#region src/properties/index.ts
59
- const PROPERTIES_KEY = "@properties";
60
- /**
61
- * Valid CSS custom property name pattern (after the -- prefix).
62
- * Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.
63
- */
64
- const VALID_PROPERTY_NAME_PATTERN = /^[a-z_][a-z0-9-_]*$/i;
65
- /**
66
- * Validate a CSS custom property name (the part after --).
67
- * Returns true if the name is valid for use as a CSS custom property.
68
- */
69
- function isValidPropertyName(name) {
70
- return VALID_PROPERTY_NAME_PATTERN.test(name);
71
- }
72
- /**
73
- * Check if styles object has local @properties definition.
74
- * Fast path: single property lookup.
75
- */
76
- function hasLocalProperties(styles) {
77
- return PROPERTIES_KEY in styles;
78
- }
79
- /**
80
- * Extract local @properties from styles object.
81
- * Returns null if no local properties (fast path).
82
- */
83
- function extractLocalProperties(styles) {
84
- const properties = styles[PROPERTIES_KEY];
85
- if (!properties || typeof properties !== "object") return null;
86
- return properties;
87
- }
88
- /**
89
- * Parse a property token name and return the CSS property name and whether it's a color.
90
- * Supports tasty token syntax and validates the property name.
91
- *
92
- * Token formats:
93
- * - `$name` → { cssName: '--name', isColor: false }
94
- * - `#name` → { cssName: '--name-color', isColor: true }
95
- * - `--name` → { cssName: '--name', isColor: false } (legacy, auto-detect color by suffix)
96
- * - `name` → { cssName: '--name', isColor: false } (legacy)
97
- *
98
- * @param token - The property token to parse
99
- * @returns Parsed result with cssName, isColor, isValid, and optional error
100
- */
101
- function parsePropertyToken(token) {
102
- if (!token || typeof token !== "string") return {
103
- cssName: "",
104
- isColor: false,
105
- isValid: false,
106
- error: "Property token must be a non-empty string"
107
- };
108
- let name;
109
- let isColor;
110
- if (token.startsWith("$")) {
111
- name = token.slice(1);
112
- isColor = false;
113
- } else if (token.startsWith("#")) {
114
- name = token.slice(1);
115
- isColor = true;
116
- } else if (token.startsWith("--")) {
117
- name = token.slice(2);
118
- isColor = token.endsWith("-color");
119
- } else {
120
- name = token;
121
- isColor = token.endsWith("-color");
122
- }
123
- if (!name) return {
124
- cssName: "",
125
- isColor,
126
- isValid: false,
127
- error: "Property name cannot be empty"
128
- };
129
- if (!isValidPropertyName(name)) return {
130
- cssName: "",
131
- isColor,
132
- isValid: false,
133
- error: `Invalid property name "${name}". Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.`
134
- };
135
- let cssName;
136
- if (token.startsWith("#")) cssName = `--${name}-color`;
137
- else cssName = `--${name}`;
138
- return {
139
- cssName,
140
- isColor,
141
- isValid: true
142
- };
143
- }
144
- /**
145
- * Normalize a property definition to a consistent string representation.
146
- * Used for comparing definitions to detect type conflicts.
147
- *
148
- * Only `syntax` and `inherits` are compared — `initialValue` is intentionally
149
- * excluded because different components may set different defaults for the
150
- * same typed property (e.g. auto-inferred `0px` vs explicit `6px`).
151
- *
152
- * Keys are sorted alphabetically to ensure consistent comparison:
153
- * { inherits: true, syntax: '<color>' } === { syntax: '<color>', inherits: true }
154
- */
155
- function normalizePropertyDefinition(def) {
156
- const normalized = {};
157
- if (def.inherits !== void 0) normalized.inherits = def.inherits;
158
- if (def.syntax !== void 0) normalized.syntax = def.syntax;
159
- return JSON.stringify(normalized);
160
- }
161
- /**
162
- * Get the effective property definition for a token.
163
- * For color tokens (#name), auto-sets syntax to '<color>' and defaults initialValue to 'transparent'.
164
- *
165
- * @param token - Property token ($name, #name, --name, or plain name)
166
- * @param userDefinition - User-provided definition options
167
- * @returns Effective definition with cssName, definition, isValid, and optional error
168
- */
169
- function getEffectiveDefinition(token, userDefinition) {
170
- const parsed = parsePropertyToken(token);
171
- if (!parsed.isValid) return {
172
- cssName: "",
173
- definition: userDefinition,
174
- isColor: false,
175
- isValid: false,
176
- error: parsed.error
177
- };
178
- if (parsed.isColor) return {
179
- cssName: parsed.cssName,
180
- definition: {
181
- syntax: "<color>",
182
- inherits: userDefinition.inherits,
183
- initialValue: userDefinition.initialValue ?? "transparent"
184
- },
185
- isColor: true,
186
- isValid: true
187
- };
188
- return {
189
- cssName: parsed.cssName,
190
- definition: userDefinition,
191
- isColor: false,
192
- isValid: true
193
- };
194
- }
195
- const UNIT_TO_SYNTAX = {};
196
- const LENGTH_UNITS = [
197
- "px",
198
- "em",
199
- "rem",
200
- "vw",
201
- "vh",
202
- "vmin",
203
- "vmax",
204
- "ch",
205
- "ex",
206
- "cap",
207
- "ic",
208
- "lh",
209
- "rlh",
210
- "svw",
211
- "svh",
212
- "lvw",
213
- "lvh",
214
- "dvw",
215
- "dvh",
216
- "cqw",
217
- "cqh",
218
- "cqi",
219
- "cqb",
220
- "cqmin",
221
- "cqmax"
222
- ];
223
- const ANGLE_UNITS = [
224
- "deg",
225
- "rad",
226
- "grad",
227
- "turn"
228
- ];
229
- const TIME_UNITS = ["ms", "s"];
230
- for (const u of LENGTH_UNITS) UNIT_TO_SYNTAX[u] = {
231
- syntax: "<length-percentage>",
232
- initialValue: "0px"
233
- };
234
- UNIT_TO_SYNTAX["%"] = {
235
- syntax: "<length-percentage>",
236
- initialValue: "0px"
237
- };
238
- for (const u of ANGLE_UNITS) UNIT_TO_SYNTAX[u] = {
239
- syntax: "<angle>",
240
- initialValue: "0deg"
241
- };
242
- for (const u of TIME_UNITS) UNIT_TO_SYNTAX[u] = {
243
- syntax: "<time>",
244
- initialValue: "0s"
245
- };
246
- /**
247
- * Infer CSS @property syntax from a concrete value.
248
- * Detects numeric types: \<number\>, \<length-percentage\>, \<angle\>, \<time\>.
249
- * Length and percentage values both map to \<length-percentage\> for maximum flexibility.
250
- * Color properties are handled separately via the #name token convention
251
- * (--name-color gets \<color\> syntax automatically in getEffectiveDefinition).
252
- *
253
- * @param value - The CSS value to infer from (e.g. '10px', '1', '45deg')
254
- * @returns Inferred syntax and initial value, or null if not inferable
255
- */
256
- function inferSyntaxFromValue(value) {
257
- if (!value || typeof value !== "string") return null;
258
- const trimmed = value.trim();
259
- if (!trimmed) return null;
260
- if (RE_NUMBER.test(trimmed)) {
261
- if (parseFloat(trimmed) === 0) return null;
262
- return {
263
- syntax: "<number>",
264
- initialValue: "0"
265
- };
266
- }
267
- const unitMatch = trimmed.match(RE_RAW_UNIT);
268
- if (unitMatch) {
269
- const mapping = UNIT_TO_SYNTAX[unitMatch[2]];
270
- if (mapping) return mapping;
271
- }
272
- return null;
273
- }
274
- //#endregion
275
1
  //#region src/parser/lru.ts
276
2
  var Lru = class {
277
3
  limit;
@@ -379,6 +105,34 @@ var Lru = class {
379
105
  }
380
106
  };
381
107
  //#endregion
108
+ //#region src/utils/function-color.ts
109
+ const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
110
+ const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
111
+ /**
112
+ * Resolve a `name(...)` value produced by a registered custom parse function
113
+ * into its concrete color output.
114
+ *
115
+ * A color function is just a `functions` entry whose output is an already
116
+ * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
117
+ * value to the global parser (which already runs the registered parse function)
118
+ * and returns the result only when it looks like a color. Returns `null` when
119
+ * `str` is not a registered custom function or its output is not a color.
120
+ *
121
+ * This is the generic replacement for the previously hardcoded okhsl/okhst
122
+ * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
123
+ * the `#token.alpha` injection path.
124
+ */
125
+ function resolveFunctionColor(str) {
126
+ const m = RE_FUNC_NAME.exec(str);
127
+ if (!m) return null;
128
+ const name = m[1].toLowerCase();
129
+ getGlobalParser();
130
+ if (!(name in getGlobalParseFunctions())) return null;
131
+ const out = getGlobalParser().process(str).output;
132
+ if (!out || !RE_COLOR_OUT.test(out)) return null;
133
+ return out;
134
+ }
135
+ //#endregion
382
136
  //#region src/utils/color-math.ts
383
137
  const OKLab_to_LMS_M = [
384
138
  [
@@ -1054,8 +808,6 @@ function strToRgb(color, _ignoreAlpha = false) {
1054
808
  if (color.startsWith("rgb")) return color;
1055
809
  if (color.startsWith("#")) return hexToRgb(color);
1056
810
  if (color.startsWith("oklch(")) return oklchStringToRgb(color);
1057
- if (color.startsWith("okhsl(")) return okhslStringToRgb(color);
1058
- if (color.startsWith("okhst(")) return okhstStringToRgb(color);
1059
811
  if (color.startsWith("hsl")) return hslStringToRgb(color);
1060
812
  const namedHex = getNamedColorHex().get(color.toLowerCase());
1061
813
  if (namedHex) return hexToRgb(namedHex);
@@ -1092,62 +844,6 @@ function hslStringToRgb(hslStr) {
1092
844
  return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
1093
845
  }
1094
846
  /**
1095
- * Convert an `okhsl()` color string to an `rgb()`/`rgba()` CSS string.
1096
- * Supports deg/turn/rad hue units and percentage saturation/lightness.
1097
- */
1098
- function okhslStringToRgb(okhslStr) {
1099
- const match = okhslStr.match(/okhsl\(([^)]+)\)/i);
1100
- if (!match) return null;
1101
- const [colorPart, alphaPart] = match[1].trim().split("/");
1102
- const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
1103
- if (parts.length < 3) return null;
1104
- let h = parseFloat(parts[0]);
1105
- const hueStr = parts[0].toLowerCase();
1106
- if (hueStr.endsWith("turn")) h = parseFloat(hueStr) * 360;
1107
- else if (hueStr.endsWith("rad")) h = parseFloat(hueStr) * 180 / Math.PI;
1108
- else if (hueStr.endsWith("deg")) h = parseFloat(hueStr);
1109
- const parsePercent = (val) => {
1110
- const num = parseFloat(val);
1111
- return val.includes("%") ? num / 100 : num;
1112
- };
1113
- const s = Math.max(0, Math.min(1, parsePercent(parts[1])));
1114
- const l = Math.max(0, Math.min(1, parsePercent(parts[2])));
1115
- const [r, g, b] = okhslToSrgb(h, s, l);
1116
- const r255 = Math.round(Math.max(0, Math.min(1, r)) * 255);
1117
- const g255 = Math.round(Math.max(0, Math.min(1, g)) * 255);
1118
- const b255 = Math.round(Math.max(0, Math.min(1, b)) * 255);
1119
- if (alphaPart) return `rgba(${r255}, ${g255}, ${b255}, ${parseFloat(alphaPart.trim())})`;
1120
- return `rgb(${r255} ${g255} ${b255})`;
1121
- }
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
- /**
1151
847
  * Convert an `oklch()` color string to an `rgb()`/`rgba()` CSS string.
1152
848
  * Supports deg/turn/rad hue units and percentage lightness.
1153
849
  */
@@ -1309,28 +1005,8 @@ function resolveToRgbaValues(color) {
1309
1005
  parsed.alpha
1310
1006
  ];
1311
1007
  }
1312
- if (trimmed.startsWith("okhsl(")) {
1313
- const parsed = parseColorFuncArgs(trimmed, "okhsl");
1314
- if (!parsed) return null;
1315
- const [r, g, b] = okhslToSrgb(parseHue(parsed.parts[0]), clamp01(parsePercent(parsed.parts[1])), clamp01(parsePercent(parsed.parts[2])));
1316
- return [
1317
- clamp01(r) * 255,
1318
- clamp01(g) * 255,
1319
- clamp01(b) * 255,
1320
- parsed.alpha
1321
- ];
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
- }
1008
+ const resolved = resolveFunctionColor(trimmed);
1009
+ if (resolved && resolved !== trimmed) return resolveToRgbaValues(resolved);
1334
1010
  const fallback = strToRgb(trimmed);
1335
1011
  if (fallback) {
1336
1012
  if (fallback !== trimmed) return resolveToRgbaValues(fallback);
@@ -1527,126 +1203,64 @@ function getComponentPropertySyntax() {
1527
1203
  }
1528
1204
  }
1529
1205
  //#endregion
1530
- //#region src/utils/hash.ts
1531
- /**
1532
- * Fast string hash (djb2) for deduplication keys.
1533
- * Not cryptographic — just collision-resistant enough for cache keys.
1534
- */
1535
- function hashString(str) {
1536
- let hash = 5381;
1537
- for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
1538
- return (hash >>> 0).toString(36);
1539
- }
1540
- //#endregion
1541
- //#region src/utils/is-dev-env.ts
1542
- /**
1543
- * Check if we're in a development environment at runtime
1544
- * Uses bracket notation to avoid bundler compilation
1545
- * Also checks for TASTY_DEBUG localStorage setting
1546
- */
1547
- function isDevEnv() {
1548
- if (typeof window !== "undefined" && window.localStorage) try {
1549
- const forceTastyDebug = window.localStorage.getItem("TASTY_DEBUG");
1550
- if (forceTastyDebug !== null && forceTastyDebug.toLowerCase() === "true") return true;
1551
- } catch {}
1552
- if (typeof process === "undefined") return false;
1553
- return true;
1554
- }
1555
- //#endregion
1556
- //#region src/utils/name-prefix.ts
1557
- /**
1558
- * Name prefix utilities for generated identifiers.
1559
- *
1560
- * Tasty generates three kinds of identifiers from content hashes:
1561
- * - class names (used in DOM `class` attribute)
1562
- * - keyframe names (used in CSS `animation`)
1563
- * - counter-style names (used in CSS `list-style-type`)
1564
- *
1565
- * All three derive from a single configurable prefix so that an app
1566
- * can namespace every identifier under one string. Discriminator letters
1567
- * (`k`, `c`) keep the three kinds visually distinct in devtools — they
1568
- * are not required for correctness (CSS keeps these in separate
1569
- * namespaces), only for readability.
1570
- *
1571
- * The runtime / SSR / RSC paths must agree on the prefix; otherwise the
1572
- * client-side hash for a given style will not match the server-rendered
1573
- * class and hydration breaks. The zero-runtime build path uses a
1574
- * different default (`'ts'`) so its classes can't collide with runtime
1575
- * (`'t'`) classes when both are loaded on the same page.
1576
- */
1577
- /** Default prefix used by the runtime / SSR / RSC paths. */
1578
- const DEFAULT_NAME_PREFIX = "t";
1579
- /** Default prefix used by the zero-runtime (`tastyStatic`) build path. */
1580
- const DEFAULT_ZERO_NAME_PREFIX = "ts";
1581
- /**
1582
- * Allowed shape: starts with a letter or underscore, then letters/
1583
- * digits/underscore/hyphen. Length capped at 32 to keep generated
1584
- * names sane. Matches the CSS identifier rules for the common case
1585
- * while keeping the surface conservative.
1586
- */
1587
- const NAME_PREFIX_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$/;
1588
- /**
1589
- * Validate a `namePrefix` value.
1590
- * Throws a TypeError with a descriptive message on invalid input so
1591
- * misconfiguration fails loudly at `configure()` time rather than
1592
- * surfacing later as broken hydration.
1593
- */
1594
- function validateNamePrefix(prefix) {
1595
- if (typeof prefix !== "string") throw new TypeError(`[Tasty] namePrefix must be a string, got ${typeof prefix}.`);
1596
- if (!NAME_PREFIX_PATTERN.test(prefix)) throw new TypeError(`[Tasty] namePrefix "${prefix}" is invalid. It must start with a letter (a-z, A-Z) or "_", contain only letters, digits, "_" or "-", and be 1-32 characters long. Examples: "t", "ts", "myapp-", "_foo".`);
1597
- }
1598
- /**
1599
- * Build a class name: `${prefix}${hash}`.
1600
- * The hash is appended verbatim — supply a separator inside the prefix
1601
- * itself if you want one (e.g. `'myapp-'`).
1602
- */
1603
- function makeClassName(prefix, hash) {
1604
- return `${prefix}${hash}`;
1605
- }
1606
- /**
1607
- * Build a keyframe name: `${prefix}k${suffix}`.
1608
- * The `k` discriminator keeps keyframe names visually distinct from
1609
- * class names sharing the same prefix. `suffix` is typically a content
1610
- * hash but may be a counter for ad-hoc allocation.
1611
- */
1612
- function makeKeyframeName(prefix, suffix) {
1613
- return `${prefix}k${suffix}`;
1614
- }
1615
- /**
1616
- * Build a counter-style name: `${prefix}c${suffix}`.
1617
- * The `c` discriminator keeps counter-style names visually distinct
1618
- * from class names sharing the same prefix.
1619
- */
1620
- function makeCounterStyleName(prefix, suffix) {
1621
- return `${prefix}c${suffix}`;
1622
- }
1623
- /** Escape a string for safe inclusion in a regex literal. */
1624
- function escapeRegex(str) {
1625
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1626
- }
1627
- /**
1628
- * Regex matching any tasty class for the given prefix.
1629
- * Used by the runtime GC's DOM scan and class-allocation bookkeeping.
1630
- */
1631
- function tastyClassRegex(prefix) {
1632
- return new RegExp(`^${escapeRegex(prefix)}[a-z0-9]+$`);
1633
- }
1634
- /**
1635
- * Global regex extracting tasty class names from RSC-inlined CSS.
1636
- * Looks for the doubled-specificity pattern `.cls.cls` that
1637
- * `formatRules()` always emits, which makes extraction reliable.
1638
- */
1639
- function rscClassRegexGlobal(prefix) {
1640
- return new RegExp(`\\.(${escapeRegex(prefix)}[a-z0-9]+)\\.\\1`, "g");
1206
+ //#region src/parser/const.ts
1207
+ const VALUE_KEYWORDS = new Set([
1208
+ "auto",
1209
+ "max-content",
1210
+ "min-content",
1211
+ "fit-content",
1212
+ "stretch",
1213
+ "initial",
1214
+ "inherit",
1215
+ "revert",
1216
+ "unset",
1217
+ "revert-layer"
1218
+ ]);
1219
+ const CSS_WIDE_KEYWORDS = new Set([
1220
+ "initial",
1221
+ "inherit",
1222
+ "revert",
1223
+ "unset",
1224
+ "revert-layer"
1225
+ ]);
1226
+ const COLOR_FUNCS = new Set([
1227
+ "rgb",
1228
+ "rgba",
1229
+ "hsl",
1230
+ "hsla",
1231
+ "hwb",
1232
+ "lab",
1233
+ "lch",
1234
+ "oklab",
1235
+ "oklch",
1236
+ "color",
1237
+ "device-cmyk",
1238
+ "gray",
1239
+ "color-mix",
1240
+ "color-contrast"
1241
+ ]);
1242
+ const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
1243
+ const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
1244
+ const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
1245
+ const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
1246
+ const CANONICAL_FUNC_CASE = new Map([
1247
+ ["translatex", "translateX"],
1248
+ ["translatey", "translateY"],
1249
+ ["translatez", "translateZ"],
1250
+ ["scalex", "scaleX"],
1251
+ ["scaley", "scaleY"],
1252
+ ["scalez", "scaleZ"],
1253
+ ["rotatex", "rotateX"],
1254
+ ["rotatey", "rotateY"],
1255
+ ["rotatez", "rotateZ"],
1256
+ ["skewx", "skewX"],
1257
+ ["skewy", "skewY"]
1258
+ ]);
1259
+ function canonicalFuncName(lowered) {
1260
+ return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
1641
1261
  }
1642
1262
  //#endregion
1643
1263
  //#region src/parser/types.ts
1644
- let Bucket = /* @__PURE__ */ function(Bucket) {
1645
- Bucket[Bucket["Color"] = 0] = "Color";
1646
- Bucket[Bucket["Value"] = 1] = "Value";
1647
- Bucket[Bucket["Mod"] = 2] = "Mod";
1648
- return Bucket;
1649
- }({});
1650
1264
  const makeEmptyPart = () => ({
1651
1265
  mods: [],
1652
1266
  values: [],
@@ -1737,17 +1351,48 @@ function classify(raw, opts, recurse) {
1737
1351
  processed: token
1738
1352
  };
1739
1353
  if (token.startsWith("$$")) {
1740
- const name = token.slice(2);
1741
- if (/^[a-z_][a-z0-9-_]*$/i.test(name)) return {
1354
+ const rest = token.slice(2);
1355
+ const openIdx = rest.indexOf("(");
1356
+ if (openIdx > 0 && rest.endsWith(")")) {
1357
+ const fname = rest.slice(0, openIdx);
1358
+ if (/^[a-z_][a-z0-9-_]*$/i.test(fname)) {
1359
+ const inner = rest.slice(openIdx + 1, -1).trim();
1360
+ const cssName = `--${fname}`;
1361
+ if (opts.functions && cssName in opts.functions) {
1362
+ const groups = inner ? new StyleParser(opts).process(inner).groups : [];
1363
+ const funcResult = opts.functions[cssName](groups);
1364
+ if (funcResult !== "") return classify(funcResult, {
1365
+ ...opts,
1366
+ functions: void 0
1367
+ }, recurse);
1368
+ }
1369
+ return {
1370
+ bucket: 1,
1371
+ processed: `--${fname}(${inner ? recurse(inner).output : ""})`
1372
+ };
1373
+ }
1374
+ }
1375
+ if (/^[a-z_][a-z0-9-_]*$/i.test(rest)) return {
1742
1376
  bucket: 1,
1743
- processed: `--${name}`
1377
+ processed: `--${rest}`
1744
1378
  };
1745
1379
  }
1746
1380
  if (token.startsWith("##")) {
1747
- const name = token.slice(2);
1748
- if (/^[a-z_][a-z0-9-_]*$/i.test(name)) return {
1381
+ const rest = token.slice(2);
1382
+ const openIdx = rest.indexOf("(");
1383
+ if (openIdx > 0 && rest.endsWith(")")) {
1384
+ const fname = rest.slice(0, openIdx);
1385
+ if (/^[a-z_][a-z0-9-_]*$/i.test(fname)) {
1386
+ const inner = rest.slice(openIdx + 1, -1).trim();
1387
+ return {
1388
+ bucket: 1,
1389
+ processed: `--${fname}-color(${inner ? recurse(inner).output : ""})`
1390
+ };
1391
+ }
1392
+ }
1393
+ if (/^[a-z_][a-z0-9-_]*$/i.test(rest)) return {
1749
1394
  bucket: 1,
1750
- processed: `--${name}-color`
1395
+ processed: `--${rest}-color`
1751
1396
  };
1752
1397
  }
1753
1398
  if (token === "#current") return {
@@ -1780,13 +1425,16 @@ function classify(raw, opts, recurse) {
1780
1425
  if (baseToken in predefinedTokens) {
1781
1426
  const resolvedValue = predefinedTokens[baseToken];
1782
1427
  if (resolvedValue.startsWith("#")) return classify(`${resolvedValue.toLowerCase()}.${rawAlpha}`, opts, recurse);
1783
- const funcMatch = resolvedValue.match(/^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|okhst|device-cmyk|gray|color-mix|color-contrast)\((.+)\)$/i);
1428
+ const funcMatch = resolvedValue.match(/^([a-z][a-z0-9-]*)\((.+)\)$/i);
1784
1429
  if (funcMatch) {
1785
1430
  const [, funcName, args] = funcMatch;
1431
+ const lowerFunc = funcName.toLowerCase();
1432
+ const isCustomFunc = !!(opts.functions && lowerFunc in opts.functions && !COLOR_FUNCS.has(lowerFunc) && !COLOR_FUNCS.has(funcName.replace(/a$/i, "").toLowerCase()));
1433
+ const normalizedFunc = isCustomFunc ? lowerFunc : funcName.replace(/a$/i, "").toLowerCase();
1434
+ if (!(COLOR_FUNCS.has(normalizedFunc) || COLOR_FUNCS.has(lowerFunc) || isCustomFunc)) return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
1786
1435
  let alpha;
1787
1436
  if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
1788
1437
  else alpha = rawAlpha === "0" ? "0" : `.${rawAlpha}`;
1789
- const normalizedFunc = funcName.replace(/a$/i, "").toLowerCase();
1790
1438
  const normalizeArgs = (a) => {
1791
1439
  let result = "";
1792
1440
  let depth = 0;
@@ -1833,7 +1481,7 @@ function classify(raw, opts, recurse) {
1833
1481
  }
1834
1482
  const hasLegacyAlpha = !hasModernAlpha && /a$/i.test(funcName) && topLevelCommaCount === 3;
1835
1483
  const constructed = `${normalizedFunc}(${hasModernAlpha || hasLegacyAlpha ? normalizeArgs(hasModernAlpha ? args.slice(0, slashIdx).trim() : args.slice(0, lastTopLevelComma).trim()) : normalizeArgs(args)} / ${alpha})`;
1836
- if (!COLOR_FUNCS.has(normalizedFunc) && opts.funcs && normalizedFunc in opts.funcs) return classify(constructed, opts, recurse);
1484
+ if (!COLOR_FUNCS.has(normalizedFunc) && opts.functions && normalizedFunc in opts.functions) return classify(constructed, opts, recurse);
1837
1485
  return {
1838
1486
  bucket: 0,
1839
1487
  processed: constructed
@@ -1887,644 +1535,1616 @@ function classify(raw, opts, recurse) {
1887
1535
  processed: `var(--${name}-color)`
1888
1536
  };
1889
1537
  }
1890
- const openIdx = token.indexOf("(");
1891
- if (openIdx > 0 && token.endsWith(")")) {
1892
- const fname = token.slice(0, openIdx);
1893
- const inner = token.slice(openIdx + 1, -1);
1894
- if (COLOR_FUNCS.has(fname)) {
1895
- const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
1896
- return {
1897
- bucket: 0,
1898
- processed: `${canonicalFuncName(fname)}(${argProcessed})`
1899
- };
1900
- }
1901
- if (opts.funcs && fname in opts.funcs) {
1902
- const tmp = new StyleParser(opts).process(inner);
1903
- return classify(opts.funcs[fname](tmp.groups), {
1904
- ...opts,
1905
- funcs: void 0
1906
- }, recurse);
1907
- }
1908
- const argProcessed = recurse(inner).output;
1909
- return {
1910
- bucket: 1,
1911
- processed: `${canonicalFuncName(fname)}(${argProcessed})`
1912
- };
1538
+ const openIdx = token.indexOf("(");
1539
+ if (openIdx > 0 && token.endsWith(")")) {
1540
+ const fname = token.slice(0, openIdx);
1541
+ const inner = token.slice(openIdx + 1, -1);
1542
+ if (COLOR_FUNCS.has(fname)) {
1543
+ const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
1544
+ return {
1545
+ bucket: 0,
1546
+ processed: `${canonicalFuncName(fname)}(${argProcessed})`
1547
+ };
1548
+ }
1549
+ if (opts.functions && fname in opts.functions) {
1550
+ const tmp = new StyleParser(opts).process(inner);
1551
+ return classify(opts.functions[fname](tmp.groups), {
1552
+ ...opts,
1553
+ functions: void 0
1554
+ }, recurse);
1555
+ }
1556
+ const argProcessed = recurse(inner).output;
1557
+ return {
1558
+ bucket: 1,
1559
+ processed: `${canonicalFuncName(fname)}(${argProcessed})`
1560
+ };
1561
+ }
1562
+ if (token.startsWith("(") && token.endsWith(")")) {
1563
+ const colorMatch = token.slice(1, -1).match(/^#([a-z0-9-]+)\s*,\s*(.*)$/i);
1564
+ if (colorMatch) {
1565
+ const [, name, fallback] = colorMatch;
1566
+ return {
1567
+ bucket: 0,
1568
+ processed: `var(--${name}-color, ${recurse(fallback).output})`
1569
+ };
1570
+ }
1571
+ }
1572
+ if (token.startsWith("(") && token.endsWith(")")) {
1573
+ const match = token.slice(1, -1).match(/^\$([a-z_][a-z0-9-_]*)\s*,\s*(.*)$/);
1574
+ if (match) {
1575
+ const [, name, fallback] = match;
1576
+ const processedFallback = recurse(fallback).output;
1577
+ return {
1578
+ bucket: name.endsWith("-color") ? 0 : 1,
1579
+ processed: `var(--${name}, ${processedFallback})`
1580
+ };
1581
+ }
1582
+ }
1583
+ if (token[0] === "(" && token[token.length - 1] === ")") return {
1584
+ bucket: 1,
1585
+ processed: `calc(${recurse(token.slice(1, -1)).output})`
1586
+ };
1587
+ const um = token.match(RE_UNIT_NUM);
1588
+ if (um) {
1589
+ const unit = um[1];
1590
+ const numericPart = parseFloat(token.slice(0, -unit.length));
1591
+ const handler = opts.units && opts.units[unit];
1592
+ if (handler) if (typeof handler === "string") {
1593
+ const rawMatch = handler.match(RE_RAW_UNIT);
1594
+ if (rawMatch) {
1595
+ const [, baseNum, cssUnit] = rawMatch;
1596
+ return {
1597
+ bucket: 1,
1598
+ processed: resolveUntilStable(`${numericPart * parseFloat(baseNum)}${cssUnit}`, opts, recurse)
1599
+ };
1600
+ }
1601
+ const base = handler;
1602
+ if (numericPart === 1) return {
1603
+ bucket: 1,
1604
+ processed: base
1605
+ };
1606
+ return {
1607
+ bucket: 1,
1608
+ processed: `calc(${numericPart} * ${base})`
1609
+ };
1610
+ } else return {
1611
+ bucket: 1,
1612
+ processed: handler(numericPart)
1613
+ };
1614
+ }
1615
+ if (/^[+-]?(?:\d*\.\d+|\d+)[a-z%]+$/.test(token)) return {
1616
+ bucket: 1,
1617
+ processed: token
1618
+ };
1619
+ if (RE_NUMBER.test(token)) return {
1620
+ bucket: 1,
1621
+ processed: token
1622
+ };
1623
+ if (VALUE_KEYWORDS.has(token)) return {
1624
+ bucket: 1,
1625
+ processed: token
1626
+ };
1627
+ if (token === "transparent" || token === "currentcolor") return {
1628
+ bucket: 0,
1629
+ processed: token
1630
+ };
1631
+ return {
1632
+ bucket: 2,
1633
+ processed: token
1634
+ };
1635
+ }
1636
+ //#endregion
1637
+ //#region src/parser/tokenizer.ts
1638
+ const isWhitespace = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
1639
+ function scan(src, cb) {
1640
+ let depth = 0;
1641
+ let inUrl = false;
1642
+ let inQuote = 0;
1643
+ let start = 0;
1644
+ let i = 0;
1645
+ let pendingSlash = false;
1646
+ const flush = (isComma, isSlash) => {
1647
+ const actualSlash = isSlash || pendingSlash;
1648
+ pendingSlash = false;
1649
+ if (start < i) {
1650
+ const prevChar = start > 0 ? src[start - 1] : null;
1651
+ cb(src.slice(start, i), isComma, actualSlash, prevChar);
1652
+ } else if (isComma) cb("", true, false, null);
1653
+ else if (actualSlash) cb("", false, true, null);
1654
+ start = i + 1;
1655
+ };
1656
+ for (; i < src.length; i++) {
1657
+ const ch = src[i];
1658
+ if (inQuote) {
1659
+ if (ch === inQuote && src[i - 1] !== "\\") inQuote = 0;
1660
+ continue;
1661
+ }
1662
+ if (ch === "\"" || ch === "'") {
1663
+ inQuote = ch;
1664
+ continue;
1665
+ }
1666
+ if (ch === "(") {
1667
+ if (!depth) {
1668
+ if (src.slice(Math.max(0, i - 3), i + 1) === "url(") inUrl = true;
1669
+ }
1670
+ depth++;
1671
+ continue;
1672
+ }
1673
+ if (ch === ")") {
1674
+ depth = Math.max(0, depth - 1);
1675
+ if (inUrl && depth === 0) inUrl = false;
1676
+ continue;
1677
+ }
1678
+ if (inUrl) continue;
1679
+ if (!depth) {
1680
+ if (ch === ",") {
1681
+ flush(true, false);
1682
+ continue;
1683
+ }
1684
+ if (ch === "/") {
1685
+ const prevIsWhitespace = isWhitespace(src[i - 1]);
1686
+ const nextIsWhitespace = isWhitespace(src[i + 1]);
1687
+ if (prevIsWhitespace && nextIsWhitespace) {
1688
+ pendingSlash = true;
1689
+ start = i + 1;
1690
+ continue;
1691
+ }
1692
+ continue;
1693
+ }
1694
+ if (isWhitespace(ch)) {
1695
+ flush(false, false);
1696
+ continue;
1697
+ }
1698
+ }
1699
+ }
1700
+ flush(false, false);
1701
+ }
1702
+ //#endregion
1703
+ //#region src/parser/parser.ts
1704
+ var StyleParser = class {
1705
+ opts;
1706
+ cache;
1707
+ constructor(opts = {}) {
1708
+ this.opts = opts;
1709
+ this.cache = new Lru(this.opts.cacheSize ?? 1e3);
1710
+ }
1711
+ process(src) {
1712
+ const key = String(src);
1713
+ const hit = this.cache.get(key);
1714
+ if (hit) return hit;
1715
+ const stripped = src.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, "").toLowerCase();
1716
+ const groups = [];
1717
+ let currentGroup = makeEmptyDetails();
1718
+ let currentPart = makeEmptyPart();
1719
+ let parts = [];
1720
+ const pushToken = (bucket, processed) => {
1721
+ if (!processed) return;
1722
+ const mergeIntoPrevUrl = () => {
1723
+ const lastIdx = currentPart.values.length - 1;
1724
+ currentPart.values[lastIdx] += ` ${processed}`;
1725
+ const lastAllIdx = currentPart.all.length - 1;
1726
+ currentPart.all[lastAllIdx] += ` ${processed}`;
1727
+ };
1728
+ if (currentPart.values.length > 0 && currentPart.values[currentPart.values.length - 1].startsWith("url(")) {
1729
+ mergeIntoPrevUrl();
1730
+ return;
1731
+ }
1732
+ switch (bucket) {
1733
+ case 0:
1734
+ currentPart.colors.push(processed);
1735
+ break;
1736
+ case 1:
1737
+ currentPart.values.push(processed);
1738
+ break;
1739
+ case 2:
1740
+ currentPart.mods.push(processed);
1741
+ break;
1742
+ }
1743
+ currentPart.all.push(processed);
1744
+ };
1745
+ const endPart = () => {
1746
+ if (currentPart.all.length > 0) {
1747
+ finalizePart(currentPart);
1748
+ parts.push(currentPart);
1749
+ }
1750
+ currentPart = makeEmptyPart();
1751
+ };
1752
+ const endGroup = () => {
1753
+ endPart();
1754
+ if (parts.length === 0) {
1755
+ parts.push(makeEmptyPart());
1756
+ finalizePart(parts[0]);
1757
+ }
1758
+ finalizeGroup(currentGroup, parts);
1759
+ groups.push(currentGroup);
1760
+ currentGroup = makeEmptyDetails();
1761
+ parts = [];
1762
+ currentPart = makeEmptyPart();
1763
+ };
1764
+ scan(stripped, (tok, isComma, isSlash, _prevChar) => {
1765
+ if (tok) {
1766
+ if (currentGroup.input) currentGroup.input += ` ${tok}`;
1767
+ else currentGroup.input = tok;
1768
+ const { bucket, processed } = classify(tok, this.opts, (sub) => this.process(sub));
1769
+ pushToken(bucket, processed);
1770
+ }
1771
+ if (isSlash) endPart();
1772
+ if (isComma) endGroup();
1773
+ });
1774
+ if (currentPart.all.length || parts.length || !groups.length) endGroup();
1775
+ const result = {
1776
+ output: groups.map((g) => g.output).join(", "),
1777
+ groups
1778
+ };
1779
+ Object.freeze(result);
1780
+ this.cache.set(key, result);
1781
+ return result;
1782
+ }
1783
+ setFunctions(functions) {
1784
+ this.opts.functions = functions;
1785
+ this.cache.clear();
1913
1786
  }
1914
- if (token.startsWith("(") && token.endsWith(")")) {
1915
- const colorMatch = token.slice(1, -1).match(/^#([a-z0-9-]+)\s*,\s*(.*)$/i);
1916
- if (colorMatch) {
1917
- const [, name, fallback] = colorMatch;
1918
- return {
1919
- bucket: 0,
1920
- processed: `var(--${name}-color, ${recurse(fallback).output})`
1921
- };
1787
+ setUnits(units) {
1788
+ this.opts.units = units;
1789
+ this.cache.clear();
1790
+ }
1791
+ updateOptions(patch) {
1792
+ Object.assign(this.opts, patch);
1793
+ if (patch.cacheSize) this.cache = new Lru(patch.cacheSize);
1794
+ else this.cache.clear();
1795
+ }
1796
+ /**
1797
+ * Clear the parser cache.
1798
+ * Call this when external state that affects parsing results has changed
1799
+ * (e.g., predefined tokens).
1800
+ */
1801
+ clearCache() {
1802
+ this.cache.clear();
1803
+ }
1804
+ /**
1805
+ * Get the current units configuration.
1806
+ */
1807
+ getUnits() {
1808
+ return this.opts.units;
1809
+ }
1810
+ };
1811
+ //#endregion
1812
+ //#region src/utils/is-dev-env.ts
1813
+ /**
1814
+ * Check if we're in a development environment at runtime
1815
+ * Uses bracket notation to avoid bundler compilation
1816
+ * Also checks for TASTY_DEBUG localStorage setting
1817
+ */
1818
+ function isDevEnv() {
1819
+ if (typeof window !== "undefined" && window.localStorage) try {
1820
+ const forceTastyDebug = window.localStorage.getItem("TASTY_DEBUG");
1821
+ if (forceTastyDebug !== null && forceTastyDebug.toLowerCase() === "true") return true;
1822
+ } catch {}
1823
+ if (typeof process === "undefined") return false;
1824
+ return true;
1825
+ }
1826
+ //#endregion
1827
+ //#region src/plugins/color-func.ts
1828
+ const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
1829
+ /**
1830
+ * Parse an angle value with optional unit.
1831
+ * Supports: deg, turn, rad, or unitless (treated as degrees).
1832
+ */
1833
+ const parseAngle = (value) => {
1834
+ const match = value.match(/^([+-]?\d*\.?\d+)(deg|turn|rad)?$/);
1835
+ if (!match) return 0;
1836
+ const num = parseFloat(match[1]);
1837
+ switch (match[2]) {
1838
+ case "turn": return num * 360;
1839
+ case "rad": return num * 180 / Math.PI;
1840
+ default: return num;
1841
+ }
1842
+ };
1843
+ /**
1844
+ * Parse a percentage value (e.g., "50%") to a 0-1 range.
1845
+ */
1846
+ const parsePercentage = (value) => {
1847
+ const match = value.match(/^([+-]?\d*\.?\d+)%?$/);
1848
+ if (!match) return 0;
1849
+ const num = parseFloat(match[1]);
1850
+ return value.includes("%") ? num / 100 : num;
1851
+ };
1852
+ /**
1853
+ * Creates a color function handler for the tasty parser.
1854
+ *
1855
+ * A color function is a custom `functions` entry that converts its arguments
1856
+ * to an `rgb(...)` string (an already-supported color), so it needs no special
1857
+ * core integration — registering it as a parse function is enough.
1858
+ *
1859
+ * @param name The name of the color function (e.g., 'okhsl', 'okhst')
1860
+ * @param convert Converts the parsed first three channel values (after angle/
1861
+ * percent normalization) to sRGB `[r, g, b]` in the 0-1 range
1862
+ * @param label Optional diagnostic label shown in dev warnings when the wrong
1863
+ * number of arguments is supplied (e.g., 'H S L'). Has no effect on parsing
1864
+ * or output.
1865
+ */
1866
+ function createColorFunc(name, convert, label) {
1867
+ const conversionCache = new Lru(500);
1868
+ const expected = label ? ` (${label})` : "";
1869
+ return (groups) => {
1870
+ if (groups.length === 0 || groups[0].all.length < 3) {
1871
+ if (isDevEnv()) console.warn(`[Tasty] ${name}(): expected 3 values${expected}, got:`, groups);
1872
+ return "rgb(0% 0% 0%)";
1922
1873
  }
1874
+ const group = groups[0];
1875
+ const tokens = group.all;
1876
+ const alpha = group.parts.length > 1 && group.parts[1].all.length > 0 ? group.parts[1].output : void 0;
1877
+ const cacheKey = tokens.slice(0, 3).join(" ") + (alpha ? ` / ${alpha}` : "");
1878
+ const cached = conversionCache.get(cacheKey);
1879
+ if (cached) return cached;
1880
+ const h = parseAngle(tokens[0]);
1881
+ const c2 = parsePercentage(tokens[1]);
1882
+ const c3 = parsePercentage(tokens[2]);
1883
+ const [r, g, b] = convert(h, clamp(c2, 0, 1), clamp(c3, 0, 1));
1884
+ const format = (n) => {
1885
+ const pct = n * 100;
1886
+ return parseFloat(pct.toFixed(1)).toString() + "%";
1887
+ };
1888
+ const result = alpha ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})` : `rgb(${format(r)} ${format(g)} ${format(b)})`;
1889
+ conversionCache.set(cacheKey, result);
1890
+ return result;
1891
+ };
1892
+ }
1893
+ //#endregion
1894
+ //#region src/plugins/okhsl-plugin.ts
1895
+ /**
1896
+ * OKHSL Plugin for Tasty
1897
+ *
1898
+ * Converts OKHSL color syntax to RGB notation.
1899
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
1900
+ *
1901
+ * Examples:
1902
+ * okhsl(240.5 50% 50%)
1903
+ * okhsl(240.5deg 50% 50%)
1904
+ * okhsl(0.25turn 50% 50%)
1905
+ * okhsl(1.57rad 50% 50%)
1906
+ */
1907
+ /**
1908
+ * The okhsl function handler for tasty parser.
1909
+ * Receives parsed style groups and returns an RGB color string.
1910
+ */
1911
+ const okhslFunction = createColorFunc("okhsl", okhslToSrgb, "H S L");
1912
+ /**
1913
+ * OKHSL Plugin for Tasty.
1914
+ *
1915
+ * Adds support for the `okhsl()` color function in tasty styles.
1916
+ *
1917
+ * @example
1918
+ * ```ts
1919
+ * import { configure } from '@tenphi/tasty';
1920
+ * import { okhslPlugin } from '@tenphi/tasty';
1921
+ *
1922
+ * configure({
1923
+ * plugins: [okhslPlugin()],
1924
+ * });
1925
+ *
1926
+ * // Now you can use okhsl in styles:
1927
+ * const Box = tasty({
1928
+ * styles: {
1929
+ * fill: 'okhsl(240 50% 50%)',
1930
+ * },
1931
+ * });
1932
+ * ```
1933
+ */
1934
+ const okhslPlugin = () => ({
1935
+ name: "okhsl",
1936
+ functions: { okhsl: okhslFunction }
1937
+ });
1938
+ //#endregion
1939
+ //#region src/plugins/okhst-plugin.ts
1940
+ /**
1941
+ * OKHST Plugin for Tasty
1942
+ *
1943
+ * Converts OKHST color syntax to RGB notation.
1944
+ * OKHST is OKHSL with the lightness axis replaced by a contrast-uniform tone axis.
1945
+ * Supports angle units: deg, turn, rad, or unitless (degrees).
1946
+ *
1947
+ * Examples:
1948
+ * okhst(240.5 50% 50%)
1949
+ * okhst(240.5deg 50% 50%)
1950
+ * okhst(0.25turn 50% 50%)
1951
+ * okhst(1.57rad 50% 50%)
1952
+ */
1953
+ /**
1954
+ * The okhst function handler for tasty parser.
1955
+ * Receives parsed style groups and returns an RGB color string.
1956
+ */
1957
+ const okhstFunction = createColorFunc("okhst", okhstToSrgb, "H S T");
1958
+ /**
1959
+ * OKHST Plugin for Tasty.
1960
+ *
1961
+ * Adds support for the `okhst()` color function in tasty styles.
1962
+ *
1963
+ * @example
1964
+ * ```ts
1965
+ * import { configure } from '@tenphi/tasty';
1966
+ * import { okhstPlugin } from '@tenphi/tasty';
1967
+ *
1968
+ * configure({
1969
+ * plugins: [okhstPlugin()],
1970
+ * });
1971
+ *
1972
+ * // Now you can use okhst in styles:
1973
+ * const Box = tasty({
1974
+ * styles: {
1975
+ * fill: 'okhst(240 50% 50%)',
1976
+ * },
1977
+ * });
1978
+ * ```
1979
+ */
1980
+ const okhstPlugin = () => ({
1981
+ name: "okhst",
1982
+ functions: { okhst: okhstFunction }
1983
+ });
1984
+ //#endregion
1985
+ //#region src/plugins/defaults.ts
1986
+ /**
1987
+ * Register the default color functions (okhsl, okhst) with the global parser.
1988
+ *
1989
+ * They are ordinary `functions` entries — the same mechanism any third-party
1990
+ * color plugin uses — so they need no special-casing in core.
1991
+ *
1992
+ * Idempotent and safe to call from the lazy parser initializer so that
1993
+ * zero-config usage keeps working. `resetGlobalParseFunctions()` re-invokes it
1994
+ * after clearing user-registered functions so the defaults survive a config reset.
1995
+ *
1996
+ * @internal
1997
+ */
1998
+ function registerDefaultFunctions() {
1999
+ customFunction("okhsl", okhslFunction);
2000
+ customFunction("okhst", okhstFunction);
2001
+ }
2002
+ //#endregion
2003
+ //#region src/utils/styles.ts
2004
+ /**
2005
+ * Normalize a color token value.
2006
+ * - Boolean `true` is converted to `'transparent'`
2007
+ * - Boolean `false` returns `null` (signals the token should be skipped)
2008
+ * - Other values are returned as-is
2009
+ *
2010
+ * @param value - The raw token value
2011
+ * @returns Normalized value or null if the token should be skipped
2012
+ */
2013
+ function normalizeColorTokenValue(value) {
2014
+ if (value === true) return "transparent";
2015
+ if (value === false) return null;
2016
+ return value;
2017
+ }
2018
+ const COLOR_VAR_PATTERN = /var\(--([a-z0-9-]+)-color/;
2019
+ const COLOR_VAR_COMPONENTS_PATTERN = /var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;
2020
+ const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
2021
+ const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
2022
+ const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
2023
+ function isSimpleColorFast(val) {
2024
+ switch (val.charCodeAt(0)) {
2025
+ case 35: return RE_HEX_COLOR.test(val);
2026
+ case 114: return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98;
2027
+ case 104: return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108;
2028
+ case 108: return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104;
2029
+ case 111: return val.startsWith("oklch(");
2030
+ case 118: return RE_VAR_COLOR.test(val);
2031
+ case 99: return val === "currentColor" || val === "currentcolor";
2032
+ case 116: return val === "transparent";
2033
+ default: return getNamedColorHex().has(val.toLowerCase());
1923
2034
  }
1924
- if (token.startsWith("(") && token.endsWith(")")) {
1925
- const match = token.slice(1, -1).match(/^\$([a-z_][a-z0-9-_]*)\s*,\s*(.*)$/);
1926
- if (match) {
1927
- const [, name, fallback] = match;
1928
- const processedFallback = recurse(fallback).output;
1929
- return {
1930
- bucket: name.endsWith("-color") ? 0 : 1,
1931
- processed: `var(--${name}, ${processedFallback})`
1932
- };
1933
- }
2035
+ }
2036
+ let colorWarningCount = 0;
2037
+ const MAX_COLOR_WARNINGS = 10;
2038
+ const CUSTOM_UNITS = {
2039
+ r: "6px",
2040
+ cr: "10px",
2041
+ bw: "1px",
2042
+ ow: "3px",
2043
+ x: "8px",
2044
+ sf: function sf(num) {
2045
+ return `minmax(0, ${num}fr)`;
1934
2046
  }
1935
- if (token[0] === "(" && token[token.length - 1] === ")") return {
1936
- bucket: 1,
1937
- processed: `calc(${recurse(token.slice(1, -1)).output})`
1938
- };
1939
- const um = token.match(RE_UNIT_NUM);
1940
- if (um) {
1941
- const unit = um[1];
1942
- const numericPart = parseFloat(token.slice(0, -unit.length));
1943
- const handler = opts.units && opts.units[unit];
1944
- if (handler) if (typeof handler === "string") {
1945
- const rawMatch = handler.match(RE_RAW_UNIT);
1946
- if (rawMatch) {
1947
- const [, baseNum, cssUnit] = rawMatch;
1948
- return {
1949
- bucket: 1,
1950
- processed: resolveUntilStable(`${numericPart * parseFloat(baseNum)}${cssUnit}`, opts, recurse)
1951
- };
1952
- }
1953
- const base = handler;
1954
- if (numericPart === 1) return {
1955
- bucket: 1,
1956
- processed: base
1957
- };
1958
- return {
1959
- bucket: 1,
1960
- processed: `calc(${numericPart} * ${base})`
1961
- };
1962
- } else return {
1963
- bucket: 1,
1964
- processed: handler(numericPart)
1965
- };
2047
+ };
2048
+ const DIRECTIONS = [
2049
+ "top",
2050
+ "right",
2051
+ "bottom",
2052
+ "left"
2053
+ ];
2054
+ let __tastyParser = null;
2055
+ function getOrCreateParser() {
2056
+ if (!__tastyParser) {
2057
+ __tastyParser = new StyleParser({ units: CUSTOM_UNITS });
2058
+ __tastyParser.setFunctions(__tastyParseFunctions);
2059
+ registerDefaultFunctions();
1966
2060
  }
1967
- if (/^[+-]?(?:\d*\.\d+|\d+)[a-z%]+$/.test(token)) return {
1968
- bucket: 1,
1969
- processed: token
1970
- };
1971
- if (RE_NUMBER.test(token)) return {
1972
- bucket: 1,
1973
- processed: token
1974
- };
1975
- if (VALUE_KEYWORDS.has(token)) return {
1976
- bucket: 1,
1977
- processed: token
1978
- };
1979
- if (token === "transparent" || token === "currentcolor") return {
1980
- bucket: 0,
1981
- processed: token
1982
- };
1983
- return {
1984
- bucket: 2,
1985
- processed: token
1986
- };
2061
+ return __tastyParser;
1987
2062
  }
1988
- //#endregion
1989
- //#region src/parser/tokenizer.ts
1990
- const isWhitespace = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
1991
- function scan(src, cb) {
1992
- let depth = 0;
1993
- let inUrl = false;
1994
- let inQuote = 0;
1995
- let start = 0;
1996
- let i = 0;
1997
- let pendingSlash = false;
1998
- const flush = (isComma, isSlash) => {
1999
- const actualSlash = isSlash || pendingSlash;
2000
- pendingSlash = false;
2001
- if (start < i) {
2002
- const prevChar = start > 0 ? src[start - 1] : null;
2003
- cb(src.slice(start, i), isComma, actualSlash, prevChar);
2004
- } else if (isComma) cb("", true, false, null);
2005
- else if (actualSlash) cb("", false, true, null);
2006
- start = i + 1;
2007
- };
2008
- for (; i < src.length; i++) {
2009
- const ch = src[i];
2010
- if (inQuote) {
2011
- if (ch === inQuote && src[i - 1] !== "\\") inQuote = 0;
2012
- continue;
2013
- }
2014
- if (ch === "\"" || ch === "'") {
2015
- inQuote = ch;
2063
+ const __tastyParseFunctions = {};
2064
+ /**
2065
+ * Register a parse-time function.
2066
+ *
2067
+ * @internal Use `configure({ functions })`. This bypasses the configuration lock
2068
+ * and is kept only for the lazy default-function bootstrap and the `@function`
2069
+ * polyfill, both of which must register after `configure()` has run.
2070
+ */
2071
+ function customFunction(name, fn) {
2072
+ __tastyParseFunctions[name] = fn;
2073
+ getOrCreateParser().setFunctions(__tastyParseFunctions);
2074
+ }
2075
+ /**
2076
+ * Reset parse functions back to the built-in set (okhsl/okhst).
2077
+ *
2078
+ * @internal Called by `resetConfig()`.
2079
+ */
2080
+ function resetGlobalParseFunctions() {
2081
+ for (const key of Object.keys(__tastyParseFunctions)) delete __tastyParseFunctions[key];
2082
+ registerDefaultFunctions();
2083
+ getOrCreateParser().setFunctions(__tastyParseFunctions);
2084
+ }
2085
+ /**
2086
+ * Get the global StyleParser instance.
2087
+ * Used by configure() to apply parser configuration.
2088
+ */
2089
+ function getGlobalParser() {
2090
+ return getOrCreateParser();
2091
+ }
2092
+ /**
2093
+ * Get the current custom functions registry.
2094
+ * Used by configure() to merge with new functions.
2095
+ */
2096
+ /**
2097
+ * The live parse-function registry.
2098
+ *
2099
+ * @internal Returns the mutable internal map; writing to it bypasses the
2100
+ * parser's cache invalidation, so the write silently does not take effect.
2101
+ * Read-only, internal use.
2102
+ */
2103
+ function getGlobalParseFunctions() {
2104
+ return __tastyParseFunctions;
2105
+ }
2106
+ /**
2107
+ * Storage for predefined tokens that are replaced during style parsing.
2108
+ * Keys are token names (with $ or # prefix), values are pre-processed CSS values.
2109
+ */
2110
+ let __globalPredefinedTokens = null;
2111
+ /**
2112
+ * Set global predefined tokens.
2113
+ * Called from configure() after processing token values.
2114
+ * Merges with existing tokens (new tokens override existing ones with same key).
2115
+ * Keys are normalized to lowercase (parser lowercases input before classification).
2116
+ * @internal
2117
+ */
2118
+ function setGlobalPredefinedTokens(tokens) {
2119
+ const normalizedTokens = {};
2120
+ for (const [key, value] of Object.entries(tokens)) {
2121
+ const lowerKey = key.toLowerCase();
2122
+ const lowerValue = value.toLowerCase();
2123
+ if (lowerKey.startsWith("#") && lowerValue === "#current") {
2124
+ 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.`);
2016
2125
  continue;
2017
2126
  }
2018
- if (ch === "(") {
2019
- if (!depth) {
2020
- if (src.slice(Math.max(0, i - 3), i + 1) === "url(") inUrl = true;
2127
+ normalizedTokens[lowerKey] = value;
2128
+ }
2129
+ __globalPredefinedTokens = __globalPredefinedTokens ? {
2130
+ ...__globalPredefinedTokens,
2131
+ ...normalizedTokens
2132
+ } : normalizedTokens;
2133
+ getOrCreateParser().clearCache();
2134
+ }
2135
+ /**
2136
+ * Get the current global predefined tokens.
2137
+ * Returns null if no tokens are configured.
2138
+ */
2139
+ function getGlobalPredefinedTokens() {
2140
+ return __globalPredefinedTokens;
2141
+ }
2142
+ /**
2143
+ * Reset global predefined tokens.
2144
+ * Used for testing.
2145
+ * @internal
2146
+ */
2147
+ function resetGlobalPredefinedTokens() {
2148
+ __globalPredefinedTokens = null;
2149
+ getOrCreateParser().clearCache();
2150
+ }
2151
+ /**
2152
+ *
2153
+ * @param {String} value
2154
+ * @param {Number} mode
2155
+ * @returns {Object<String,String|Array>}
2156
+ */
2157
+ function parseStyle(value) {
2158
+ let str;
2159
+ if (typeof value === "string") str = value;
2160
+ else if (typeof value === "number") str = String(value);
2161
+ else str = "";
2162
+ return getOrCreateParser().process(str);
2163
+ }
2164
+ /**
2165
+ * Parse color. Find it value, name and opacity.
2166
+ * Optimized to avoid heavy parseStyle calls for simple color patterns.
2167
+ */
2168
+ function parseColor(val, ignoreError = false) {
2169
+ if (typeof val !== "string") val = String(val ?? "");
2170
+ val = val.trim();
2171
+ if (!val) return {};
2172
+ const isSimpleColor = isSimpleColorFast(val);
2173
+ let firstColor;
2174
+ if (isSimpleColor) firstColor = val;
2175
+ else {
2176
+ const extractedColor = parseStyle(val).groups.find((g) => g.colors.length)?.colors[0];
2177
+ if (!extractedColor) {
2178
+ if (!ignoreError && colorWarningCount < MAX_COLOR_WARNINGS) {
2179
+ console.warn("[Tasty] unable to parse color:", val);
2180
+ colorWarningCount++;
2181
+ if (colorWarningCount === MAX_COLOR_WARNINGS) console.warn("[Tasty] color parsing warnings will be suppressed from now on");
2021
2182
  }
2022
- depth++;
2023
- continue;
2183
+ return {};
2024
2184
  }
2025
- if (ch === ")") {
2026
- depth = Math.max(0, depth - 1);
2027
- if (inUrl && depth === 0) inUrl = false;
2028
- continue;
2185
+ firstColor = extractedColor;
2186
+ }
2187
+ let nameMatch = firstColor.match(COLOR_VAR_PATTERN);
2188
+ if (!nameMatch) nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2189
+ let opacity;
2190
+ if (firstColor.startsWith("rgb") || firstColor.startsWith("hsl") || firstColor.startsWith("lch") || firstColor.startsWith("oklch")) {
2191
+ const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);
2192
+ if (alphaMatch) {
2193
+ const v = parseFloat(alphaMatch[1]);
2194
+ if (!isNaN(v)) opacity = v * 100;
2029
2195
  }
2030
- if (inUrl) continue;
2031
- if (!depth) {
2032
- if (ch === ",") {
2033
- flush(true, false);
2034
- continue;
2035
- }
2036
- if (ch === "/") {
2037
- const prevIsWhitespace = isWhitespace(src[i - 1]);
2038
- const nextIsWhitespace = isWhitespace(src[i + 1]);
2039
- if (prevIsWhitespace && nextIsWhitespace) {
2040
- pendingSlash = true;
2041
- start = i + 1;
2042
- continue;
2196
+ }
2197
+ return {
2198
+ color: firstColor,
2199
+ name: nameMatch ? nameMatch[1] : void 0,
2200
+ opacity
2201
+ };
2202
+ }
2203
+ function filterMods(mods, allowedMods) {
2204
+ return mods.filter((mod) => allowedMods.includes(mod));
2205
+ }
2206
+ const _innerCache = /* @__PURE__ */ new WeakMap();
2207
+ const _topLevelCache = /* @__PURE__ */ new WeakMap();
2208
+ function stringifyStyles(styles) {
2209
+ if (styles == null || typeof styles !== "object") return "";
2210
+ const cached = _topLevelCache.get(styles);
2211
+ if (cached !== void 0) return cached;
2212
+ const obj = styles;
2213
+ const keys = Object.keys(obj).sort();
2214
+ const parts = [];
2215
+ for (const k of keys) {
2216
+ const v = obj[k];
2217
+ if (v === void 0 || typeof v === "function" || typeof v === "symbol") continue;
2218
+ const c0 = k.charCodeAt(0);
2219
+ const needsInnerSort = (c0 >= 65 && c0 <= 90 || c0 === 38) && v && typeof v === "object" && !Array.isArray(v);
2220
+ let sv;
2221
+ if (needsInnerSort) {
2222
+ sv = _innerCache.get(v);
2223
+ if (sv === void 0) {
2224
+ const innerObj = v;
2225
+ const innerKeys = Object.keys(innerObj).sort();
2226
+ const innerParts = [];
2227
+ for (const ik of innerKeys) {
2228
+ const ivs = JSON.stringify(innerObj[ik]);
2229
+ if (ivs !== void 0) innerParts.push(JSON.stringify(ik) + ":" + ivs);
2043
2230
  }
2044
- continue;
2045
- }
2046
- if (isWhitespace(ch)) {
2047
- flush(false, false);
2048
- continue;
2231
+ sv = "{" + innerParts.join(",") + "}";
2232
+ _innerCache.set(v, sv);
2049
2233
  }
2050
- }
2234
+ } else sv = JSON.stringify(v);
2235
+ parts.push(JSON.stringify(k) + ":" + sv);
2051
2236
  }
2052
- flush(false, false);
2237
+ const result = "{" + parts.join(",") + "}";
2238
+ _topLevelCache.set(styles, result);
2239
+ return result;
2053
2240
  }
2054
2241
  //#endregion
2055
- //#region src/parser/parser.ts
2056
- var StyleParser = class {
2057
- opts;
2058
- cache;
2059
- constructor(opts = {}) {
2060
- this.opts = opts;
2061
- this.cache = new Lru(this.opts.cacheSize ?? 1e3);
2242
+ //#region src/functions/index.ts
2243
+ const FUNCTION_KEY = "@function";
2244
+ /** Descriptor keys that are not local-variable declarations. */
2245
+ const RESERVED_KEYS = new Set([
2246
+ "args",
2247
+ "returns",
2248
+ "result"
2249
+ ]);
2250
+ /**
2251
+ * Check if styles object has a local @function definition.
2252
+ */
2253
+ function hasLocalFunctions(styles) {
2254
+ return FUNCTION_KEY in styles;
2255
+ }
2256
+ /**
2257
+ * Extract local @function definitions from a styles object.
2258
+ * Returns null if none (fast path).
2259
+ */
2260
+ function extractLocalFunctions(styles) {
2261
+ const functions = styles[FUNCTION_KEY];
2262
+ if (!functions || typeof functions !== "object") return null;
2263
+ return functions;
2264
+ }
2265
+ /**
2266
+ * Parse a function name token into its CSS custom name.
2267
+ * Accepts `$$name`, `$name`, `--name`, or bare `name` → `--name`.
2268
+ */
2269
+ function parseFunctionName(token) {
2270
+ if (token.startsWith("$$")) return `--${token.slice(2)}`;
2271
+ if (token.startsWith("$")) return `--${token.slice(1)}`;
2272
+ if (token.startsWith("--")) return token;
2273
+ return `--${token}`;
2274
+ }
2275
+ /**
2276
+ * Parse a parameter / local-variable name token into its CSS custom name.
2277
+ * Accepts `$name`, `--name`, or bare `name` → `--name`.
2278
+ */
2279
+ function parseParamName(token) {
2280
+ if (token.startsWith("$")) return `--${token.slice(1)}`;
2281
+ if (token.startsWith("--")) return token;
2282
+ return `--${token}`;
2283
+ }
2284
+ /**
2285
+ * Format a single parameter (`--name <type>: default`).
2286
+ */
2287
+ function formatParameter(token, param) {
2288
+ const name = parseParamName(token);
2289
+ if (param === true) return name;
2290
+ if (typeof param === "string") return param ? `${name} ${param}` : name;
2291
+ let out = name;
2292
+ if (param.syntax) out += ` ${param.syntax}`;
2293
+ if (param.default !== void 0) out += `: ${parseStyle(param.default).output}`;
2294
+ return out;
2295
+ }
2296
+ /**
2297
+ * Format the function prelude: `@function --name(params) returns <type>`.
2298
+ */
2299
+ function formatFunctionPrelude(name, args, returns) {
2300
+ const cssName = parseFunctionName(name);
2301
+ let params = "";
2302
+ if (Array.isArray(args)) params = args.map((token) => formatParameter(token, true)).join(", ");
2303
+ else if (args) params = Object.entries(args).map(([token, param]) => formatParameter(token, param)).join(", ");
2304
+ const returnsPart = returns ? ` returns ${returns}` : "";
2305
+ return `@function ${cssName}(${params})${returnsPart}`;
2306
+ }
2307
+ /**
2308
+ * Format the inner declarations of a @function rule (local variables + result).
2309
+ */
2310
+ function formatFunctionDeclarations(def) {
2311
+ const parts = [];
2312
+ for (const key of Object.keys(def)) {
2313
+ if (RESERVED_KEYS.has(key) || !key.startsWith("$")) continue;
2314
+ const value = def[key];
2315
+ if (value === void 0) continue;
2316
+ parts.push(`${parseParamName(key)}: ${parseStyle(value).output};`);
2062
2317
  }
2063
- process(src) {
2064
- const key = String(src);
2065
- const hit = this.cache.get(key);
2066
- if (hit) return hit;
2067
- const stripped = src.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, "").toLowerCase();
2068
- const groups = [];
2069
- let currentGroup = makeEmptyDetails();
2070
- let currentPart = makeEmptyPart();
2071
- let parts = [];
2072
- const pushToken = (bucket, processed) => {
2073
- if (!processed) return;
2074
- const mergeIntoPrevUrl = () => {
2075
- const lastIdx = currentPart.values.length - 1;
2076
- currentPart.values[lastIdx] += ` ${processed}`;
2077
- const lastAllIdx = currentPart.all.length - 1;
2078
- currentPart.all[lastAllIdx] += ` ${processed}`;
2079
- };
2080
- if (currentPart.values.length > 0 && currentPart.values[currentPart.values.length - 1].startsWith("url(")) {
2081
- mergeIntoPrevUrl();
2082
- return;
2083
- }
2084
- switch (bucket) {
2085
- case 0:
2086
- currentPart.colors.push(processed);
2087
- break;
2088
- case 1:
2089
- currentPart.values.push(processed);
2090
- break;
2091
- case 2:
2092
- currentPart.mods.push(processed);
2093
- break;
2094
- }
2095
- currentPart.all.push(processed);
2096
- };
2097
- const endPart = () => {
2098
- if (currentPart.all.length > 0) {
2099
- finalizePart(currentPart);
2100
- parts.push(currentPart);
2101
- }
2102
- currentPart = makeEmptyPart();
2103
- };
2104
- const endGroup = () => {
2105
- endPart();
2106
- if (parts.length === 0) {
2107
- parts.push(makeEmptyPart());
2108
- finalizePart(parts[0]);
2318
+ parts.push(`result: ${parseStyle(def.result).output};`);
2319
+ return parts.join(" ");
2320
+ }
2321
+ /**
2322
+ * Format a complete @function rule as CSS.
2323
+ */
2324
+ function formatFunctionRule(name, def) {
2325
+ return `${formatFunctionPrelude(name, def.args, def.returns)} { ${formatFunctionDeclarations(def)} }`;
2326
+ }
2327
+ /** A key is a CSS-function key when it carries the `$`/`--` prefix. */
2328
+ function isCssFunctionKey(key) {
2329
+ return key.startsWith("$") || key.startsWith("--");
2330
+ }
2331
+ /** Discriminate a unified `functions` entry by its value type. */
2332
+ function isParseFunction(value) {
2333
+ return typeof value === "function";
2334
+ }
2335
+ /**
2336
+ * Split the unified `functions` map into the two internal registries:
2337
+ * - `parseFuncs`: JS parse functions (bare keys).
2338
+ * - `functionDefs`: declarative CSS `@function` definitions (`$$` keys).
2339
+ *
2340
+ * Entries whose key prefix does not match the value type are reported via
2341
+ * `onMismatch` and skipped.
2342
+ */
2343
+ function splitFunctions(map, onMismatch) {
2344
+ const parseFuncs = {};
2345
+ const functionDefs = {};
2346
+ for (const [key, value] of Object.entries(map)) {
2347
+ const cssKey = isCssFunctionKey(key);
2348
+ if (isParseFunction(value)) {
2349
+ if (cssKey) {
2350
+ onMismatch?.(key, "expected-definition");
2351
+ continue;
2109
2352
  }
2110
- finalizeGroup(currentGroup, parts);
2111
- groups.push(currentGroup);
2112
- currentGroup = makeEmptyDetails();
2113
- parts = [];
2114
- currentPart = makeEmptyPart();
2115
- };
2116
- scan(stripped, (tok, isComma, isSlash, _prevChar) => {
2117
- if (tok) {
2118
- if (currentGroup.input) currentGroup.input += ` ${tok}`;
2119
- else currentGroup.input = tok;
2120
- const { bucket, processed } = classify(tok, this.opts, (sub) => this.process(sub));
2121
- pushToken(bucket, processed);
2353
+ parseFuncs[key] = value;
2354
+ } else {
2355
+ if (!cssKey) {
2356
+ onMismatch?.(key, "expected-parse-function");
2357
+ continue;
2122
2358
  }
2123
- if (isSlash) endPart();
2124
- if (isComma) endGroup();
2125
- });
2126
- if (currentPart.all.length || parts.length || !groups.length) endGroup();
2127
- const result = {
2128
- output: groups.map((g) => g.output).join(", "),
2129
- groups
2130
- };
2131
- Object.freeze(result);
2132
- this.cache.set(key, result);
2133
- return result;
2134
- }
2135
- setFuncs(funcs) {
2136
- this.opts.funcs = funcs;
2137
- this.cache.clear();
2138
- }
2139
- setUnits(units) {
2140
- this.opts.units = units;
2141
- this.cache.clear();
2142
- }
2143
- updateOptions(patch) {
2144
- Object.assign(this.opts, patch);
2145
- if (patch.cacheSize) this.cache = new Lru(patch.cacheSize);
2146
- else this.cache.clear();
2359
+ functionDefs[key] = value;
2360
+ }
2147
2361
  }
2148
- /**
2149
- * Clear the parser cache.
2150
- * Call this when external state that affects parsing results has changed
2151
- * (e.g., predefined tokens).
2152
- */
2153
- clearCache() {
2154
- this.cache.clear();
2362
+ return {
2363
+ parseFuncs,
2364
+ functionDefs
2365
+ };
2366
+ }
2367
+ function escapeRegExp(str) {
2368
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2369
+ }
2370
+ const paramBareName = (token) => parseParamName(token).slice(2);
2371
+ /**
2372
+ * Rewrite `var(--name)` references for declared parameter/local names to a
2373
+ * collision-resistant, function-scoped prefix. This guarantees that later
2374
+ * value substitution only ever targets the function's own variables and can
2375
+ * never accidentally match a real element-level `var(--name)` introduced by an
2376
+ * argument value (e.g. the `--gap` used by Tasty units). Blanket prefix — no
2377
+ * exact-collision detection.
2378
+ */
2379
+ function prefixDeclaredVars(template, declared, prefixOf) {
2380
+ let out = template;
2381
+ for (const bare of declared) {
2382
+ const re = new RegExp(`var\\(\\s*--${escapeRegExp(bare)}(?![\\w-])`, "g");
2383
+ out = out.replace(re, `var(--${prefixOf(bare)}`);
2384
+ }
2385
+ return out;
2386
+ }
2387
+ /**
2388
+ * Replace the prefixed `var(--<prefixed>)` / `var(--<prefixed>, fallback)`
2389
+ * references with their mapped values, iterating so that local variables that
2390
+ * reference parameters (or other locals) resolve fully.
2391
+ */
2392
+ function substituteVars(template, valueMap) {
2393
+ let out = template;
2394
+ for (let iter = 0; iter < 30; iter++) {
2395
+ let changed = false;
2396
+ for (const [name, value] of valueMap) {
2397
+ const re = new RegExp(`var\\(\\s*--${escapeRegExp(name)}\\s*(?:,[^)]*)?\\)`, "g");
2398
+ out = out.replace(re, () => {
2399
+ changed = true;
2400
+ return value;
2401
+ });
2402
+ }
2403
+ if (!changed) break;
2155
2404
  }
2156
- /**
2157
- * Get the current units configuration.
2158
- */
2159
- getUnits() {
2160
- return this.opts.units;
2405
+ return out;
2406
+ }
2407
+ /** Function names currently being expanded, to guard against recursion cycles. */
2408
+ const inProgress = /* @__PURE__ */ new Set();
2409
+ /**
2410
+ * Compile a declarative `@function` definition into a parse-function closure
2411
+ * that fully inlines the call into plain CSS (the `@function` polyfill).
2412
+ *
2413
+ * Parameters and local variables are substituted directly with their values,
2414
+ * so no function-internal custom properties are emitted (collisions are
2415
+ * structurally impossible). Self/mutually-recursive functions are not expanded:
2416
+ * the cycle guard bails and the call is left untouched.
2417
+ */
2418
+ function compileFunctionClosure(name, def) {
2419
+ const cssName = parseFunctionName(name);
2420
+ const prefixOf = (bare) => `tf-${cssName.slice(2)}-${bare}`;
2421
+ const params = [];
2422
+ if (Array.isArray(def.args)) for (const token of def.args) params.push({ prefixed: prefixOf(paramBareName(token)) });
2423
+ else if (def.args) for (const [token, param] of Object.entries(def.args)) {
2424
+ let dflt;
2425
+ if (param && typeof param === "object" && param.default !== void 0) dflt = parseStyle(param.default).output;
2426
+ params.push({
2427
+ prefixed: prefixOf(paramBareName(token)),
2428
+ default: dflt
2429
+ });
2161
2430
  }
2162
- };
2431
+ const localKeys = Object.keys(def).filter((key) => !RESERVED_KEYS.has(key) && key.startsWith("$"));
2432
+ const declared = [...Array.isArray(def.args) ? def.args.map(paramBareName) : def.args ? Object.keys(def.args).map(paramBareName) : [], ...localKeys.map(paramBareName)];
2433
+ let built = false;
2434
+ let resultTemplate = "";
2435
+ const localTemplates = [];
2436
+ const build = () => {
2437
+ built = true;
2438
+ resultTemplate = prefixDeclaredVars(parseStyle(def.result).output, declared, prefixOf);
2439
+ for (const key of localKeys) {
2440
+ const value = def[key];
2441
+ if (value === void 0) continue;
2442
+ localTemplates.push({
2443
+ prefixed: prefixOf(paramBareName(key)),
2444
+ value: prefixDeclaredVars(parseStyle(value).output, declared, prefixOf)
2445
+ });
2446
+ }
2447
+ };
2448
+ return (groups) => {
2449
+ if (inProgress.has(cssName)) return "";
2450
+ inProgress.add(cssName);
2451
+ try {
2452
+ if (!built) build();
2453
+ const valueMap = /* @__PURE__ */ new Map();
2454
+ params.forEach((param, index) => {
2455
+ const arg = groups[index]?.output?.trim();
2456
+ valueMap.set(param.prefixed, arg || param.default || "");
2457
+ });
2458
+ for (const local of localTemplates) valueMap.set(local.prefixed, local.value);
2459
+ return substituteVars(resultTemplate, valueMap);
2460
+ } finally {
2461
+ inProgress.delete(cssName);
2462
+ }
2463
+ };
2464
+ }
2465
+ /** CSS function name -> serialized definition, to dedupe polyfill registration. */
2466
+ const registeredFunctionDefs = /* @__PURE__ */ new Map();
2467
+ /**
2468
+ * Register a declarative `@function` as a parse-function closure so the parser
2469
+ * inlines its call sites. Deduplicated by name + definition; a later, different
2470
+ * definition of the same name overrides the previous one (local wins).
2471
+ */
2472
+ function registerFunctionPolyfill(name, definition) {
2473
+ const cssName = parseFunctionName(name);
2474
+ const serialized = JSON.stringify(definition);
2475
+ if (registeredFunctionDefs.get(cssName) === serialized) return;
2476
+ registeredFunctionDefs.set(cssName, serialized);
2477
+ customFunction(cssName, compileFunctionClosure(name, definition));
2478
+ }
2479
+ /** Register all local `@function` definitions from a styles object (polyfill). */
2480
+ function registerLocalFunctionPolyfills(styles) {
2481
+ const local = extractLocalFunctions(styles);
2482
+ if (!local) return;
2483
+ for (const [name, definition] of Object.entries(local)) registerFunctionPolyfill(name, definition);
2484
+ }
2485
+ /** Clear polyfill registration bookkeeping (used by resetConfig in tests). */
2486
+ function resetFunctionPolyfills() {
2487
+ registeredFunctionDefs.clear();
2488
+ }
2163
2489
  //#endregion
2164
- //#region src/plugins/color-func.ts
2165
- const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
2490
+ //#region src/chunks/style-chunk-map.ts
2491
+ /**
2492
+ * Chunk names, the built-in chunk style lists, and the style→chunk lookup.
2493
+ *
2494
+ * This is a **leaf module with no imports**. It exists separately from
2495
+ * `definitions.ts` for two reasons:
2496
+ *
2497
+ * 1. The style handler registry (`styles/predefined.ts`) needs to assign chunk
2498
+ * membership for custom handlers. Reaching for `definitions.ts` would create an
2499
+ * import cycle (`definitions` -> `pipeline` -> `styles` -> `predefined`).
2500
+ * 2. `STYLE_TO_CHUNK` is populated as a side effect of module load, so it must be
2501
+ * populated by the module that *owns* it. Otherwise an importer that only pulls
2502
+ * in this module would see an empty map and misclassify every style as `misc`.
2503
+ *
2504
+ * `definitions.ts` re-exports everything here, so existing import paths and the
2505
+ * public API surface are unchanged.
2506
+ */
2507
+ const CHUNK_NAMES = {
2508
+ /** Special chunk for styles that cannot be split */
2509
+ COMBINED: "combined",
2510
+ SUBCOMPONENTS: "subcomponents",
2511
+ APPEARANCE: "appearance",
2512
+ FONT: "font",
2513
+ DIMENSION: "dimension",
2514
+ DISPLAY: "display",
2515
+ LAYOUT: "layout",
2516
+ POSITION: "position",
2517
+ MISC: "misc"
2518
+ };
2519
+ /**
2520
+ * Appearance chunk - visual styling with independent handlers
2521
+ *
2522
+ * @public
2523
+ */
2524
+ const APPEARANCE_CHUNK_STYLES = [
2525
+ "fill",
2526
+ "color",
2527
+ "opacity",
2528
+ "border",
2529
+ "radius",
2530
+ "outline",
2531
+ "outlineOffset",
2532
+ "shadow",
2533
+ "fade"
2534
+ ];
2535
+ /**
2536
+ * Font chunk - typography styles
2537
+ *
2538
+ * Handler dependencies (all styles in each handler MUST stay in this chunk):
2539
+ * ⚠️ presetStyle: preset, fontSize, lineHeight, letterSpacing, textTransform,
2540
+ * fontWeight, fontStyle, font
2541
+ */
2542
+ /** @public */
2543
+ const FONT_CHUNK_STYLES = [
2544
+ "preset",
2545
+ "font",
2546
+ "fontWeight",
2547
+ "fontStyle",
2548
+ "fontSize",
2549
+ "lineHeight",
2550
+ "letterSpacing",
2551
+ "textTransform",
2552
+ "fontFamily",
2553
+ "textAlign",
2554
+ "textDecoration",
2555
+ "wordBreak",
2556
+ "wordWrap",
2557
+ "boldFontWeight"
2558
+ ];
2559
+ /**
2560
+ * Dimension chunk - sizing and spacing
2561
+ *
2562
+ * Handler dependencies (all styles in each handler MUST stay in this chunk):
2563
+ * ⚠️ paddingStyle: padding, paddingTop/Right/Bottom/Left, paddingBlock/Inline
2564
+ * ⚠️ marginStyle: margin, marginTop/Right/Bottom/Left, marginBlock/Inline
2565
+ * ⚠️ widthStyle: width, minWidth, maxWidth
2566
+ * ⚠️ heightStyle: height, minHeight, maxHeight
2567
+ */
2568
+ /** @public */
2569
+ const DIMENSION_CHUNK_STYLES = [
2570
+ "padding",
2571
+ "paddingTop",
2572
+ "paddingRight",
2573
+ "paddingBottom",
2574
+ "paddingLeft",
2575
+ "paddingBlock",
2576
+ "paddingInline",
2577
+ "margin",
2578
+ "marginTop",
2579
+ "marginRight",
2580
+ "marginBottom",
2581
+ "marginLeft",
2582
+ "marginBlock",
2583
+ "marginInline",
2584
+ "width",
2585
+ "minWidth",
2586
+ "maxWidth",
2587
+ "height",
2588
+ "minHeight",
2589
+ "maxHeight",
2590
+ "flexBasis",
2591
+ "flexGrow",
2592
+ "flexShrink",
2593
+ "flex"
2594
+ ];
2595
+ /**
2596
+ * Display chunk - display mode, layout flow, text overflow, and scrollbar
2597
+ *
2598
+ * Handler dependencies (all styles in each handler MUST stay in this chunk):
2599
+ * ⚠️ displayStyle: display, hide, textOverflow, overflow, whiteSpace
2600
+ * ⚠️ flowStyle: display, flow
2601
+ * ⚠️ gapStyle: display, flow, gap
2602
+ * ⚠️ scrollbarStyle: scrollbar, overflow
2603
+ */
2604
+ /** @public */
2605
+ const DISPLAY_CHUNK_STYLES = [
2606
+ "display",
2607
+ "hide",
2608
+ "textOverflow",
2609
+ "overflow",
2610
+ "whiteSpace",
2611
+ "flow",
2612
+ "gap",
2613
+ "scrollbar"
2614
+ ];
2615
+ /**
2616
+ * Layout chunk - flex/grid alignment and grid templates
2617
+ *
2618
+ * Note: flow and gap are in DISPLAY chunk due to handler dependencies
2619
+ * (flowStyle and gapStyle both require 'display' prop).
2620
+ */
2621
+ /** @public */
2622
+ const LAYOUT_CHUNK_STYLES = [
2623
+ "placeItems",
2624
+ "placeContent",
2625
+ "alignItems",
2626
+ "alignContent",
2627
+ "justifyItems",
2628
+ "justifyContent",
2629
+ "align",
2630
+ "justify",
2631
+ "place",
2632
+ "columnGap",
2633
+ "rowGap",
2634
+ "gridColumns",
2635
+ "gridRows",
2636
+ "gridTemplate",
2637
+ "gridAreas",
2638
+ "gridAutoFlow",
2639
+ "gridAutoColumns",
2640
+ "gridAutoRows"
2641
+ ];
2166
2642
  /**
2167
- * Parse an angle value with optional unit.
2168
- * Supports: deg, turn, rad, or unitless (treated as degrees).
2643
+ * Position chunk - element positioning
2644
+ *
2645
+ * Handler dependencies (all styles in each handler MUST stay in this chunk):
2646
+ * ⚠️ insetStyle: inset, insetBlock, insetInline, top, right, bottom, left
2169
2647
  */
2170
- const parseAngle = (value) => {
2171
- const match = value.match(/^([+-]?\d*\.?\d+)(deg|turn|rad)?$/);
2172
- if (!match) return 0;
2173
- const num = parseFloat(match[1]);
2174
- switch (match[2]) {
2175
- case "turn": return num * 360;
2176
- case "rad": return num * 180 / Math.PI;
2177
- default: return num;
2178
- }
2179
- };
2648
+ /** @public */
2649
+ const POSITION_CHUNK_STYLES = [
2650
+ "position",
2651
+ "inset",
2652
+ "insetBlock",
2653
+ "insetInline",
2654
+ "top",
2655
+ "right",
2656
+ "bottom",
2657
+ "left",
2658
+ "zIndex",
2659
+ "gridArea",
2660
+ "gridColumn",
2661
+ "gridRow",
2662
+ "order",
2663
+ "placeSelf",
2664
+ "alignSelf",
2665
+ "justifySelf",
2666
+ "transform",
2667
+ "transition",
2668
+ "animation"
2669
+ ];
2180
2670
  /**
2181
- * Parse a percentage value (e.g., "50%") to a 0-1 range.
2671
+ * Map for O(1) style-to-chunk lookup. Seeded with the built-in lists above and
2672
+ * extended by {@link assignStyleChunk} when a custom handler declares style names
2673
+ * the built-in lists don't know.
2182
2674
  */
2183
- const parsePercentage = (value) => {
2184
- const match = value.match(/^([+-]?\d*\.?\d+)%?$/);
2185
- if (!match) return 0;
2186
- const num = parseFloat(match[1]);
2187
- return value.includes("%") ? num / 100 : num;
2188
- };
2675
+ const STYLE_TO_CHUNK = /* @__PURE__ */ new Map();
2676
+ /** Built-in assignments, captured so `resetStyleChunks()` can restore them. */
2677
+ let builtinSnapshot = null;
2678
+ function populateStyleToChunkMap() {
2679
+ for (const style of APPEARANCE_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.APPEARANCE);
2680
+ for (const style of FONT_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.FONT);
2681
+ for (const style of DIMENSION_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DIMENSION);
2682
+ for (const style of DISPLAY_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DISPLAY);
2683
+ for (const style of LAYOUT_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.LAYOUT);
2684
+ for (const style of POSITION_CHUNK_STYLES) STYLE_TO_CHUNK.set(style, CHUNK_NAMES.POSITION);
2685
+ }
2686
+ populateStyleToChunkMap();
2687
+ builtinSnapshot = new Map(STYLE_TO_CHUNK);
2189
2688
  /**
2190
- * Creates a color function handler for the tasty parser.
2191
- * @param name The name of the color space (e.g., 'okhsl', 'okhst')
2192
- * @param channelLabel The label for the channels in warnings (e.g., 'H S L')
2193
- * @param convert A function that converts the parsed H, C2, C3 values to sRGB [r, g, b] (0-1)
2689
+ * Assign a style name to a chunk.
2690
+ *
2691
+ * Used to keep a custom handler's dependencies in one chunk: chunks are rendered
2692
+ * and cached independently, and a chunk's cache key covers only its own style
2693
+ * values, so a handler whose `__lookupStyles` straddle two chunks is invoked once
2694
+ * per chunk with a subset of its inputs and can emit stale CSS.
2194
2695
  */
2195
- function createColorFunc(name, channelLabel, convert) {
2196
- const conversionCache = new Lru(500);
2197
- return (groups) => {
2198
- if (groups.length === 0 || groups[0].all.length < 3) {
2199
- console.warn(`[${name}] Expected 3 values (${channelLabel}), got:`, groups);
2200
- return "rgb(0% 0% 0%)";
2201
- }
2202
- const group = groups[0];
2203
- const tokens = group.all;
2204
- const alpha = group.parts.length > 1 && group.parts[1].all.length > 0 ? group.parts[1].output : void 0;
2205
- const cacheKey = tokens.slice(0, 3).join(" ") + (alpha ? ` / ${alpha}` : "");
2206
- const cached = conversionCache.get(cacheKey);
2207
- if (cached) return cached;
2208
- const h = parseAngle(tokens[0]);
2209
- const c2 = parsePercentage(tokens[1]);
2210
- const c3 = parsePercentage(tokens[2]);
2211
- const [r, g, b] = convert(h, clamp(c2, 0, 1), clamp(c3, 0, 1));
2212
- const format = (n) => {
2213
- const pct = n * 100;
2214
- return parseFloat(pct.toFixed(1)).toString() + "%";
2215
- };
2216
- const result = alpha ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})` : `rgb(${format(r)} ${format(g)} ${format(b)})`;
2217
- conversionCache.set(cacheKey, result);
2218
- return result;
2219
- };
2696
+ function assignStyleChunk(styleName, chunk) {
2697
+ STYLE_TO_CHUNK.set(styleName, chunk);
2220
2698
  }
2221
- //#endregion
2222
- //#region src/plugins/okhsl-plugin.ts
2223
2699
  /**
2224
- * OKHSL Plugin for Tasty
2700
+ * Drop custom chunk assignments and restore the built-in baseline.
2225
2701
  *
2226
- * Converts OKHSL color syntax to RGB notation.
2227
- * Supports angle units: deg, turn, rad, or unitless (degrees).
2702
+ * Called by `resetConfig()`. Without this, a custom handler's chunk assignments
2703
+ * would leak across tests the same way custom handlers themselves did before
2704
+ * `resetHandlers()` existed.
2228
2705
  *
2229
- * Examples:
2230
- * okhsl(240.5 50% 50%)
2231
- * okhsl(240.5deg 50% 50%)
2232
- * okhsl(0.25turn 50% 50%)
2233
- * okhsl(1.57rad 50% 50%)
2234
- */
2235
- /**
2236
- * The okhsl function handler for tasty parser.
2237
- * Receives parsed style groups and returns an RGB color string.
2706
+ * @internal
2238
2707
  */
2239
- const okhslFunc = createColorFunc("okhsl", "H S L", okhslToSrgb);
2708
+ function resetStyleChunks() {
2709
+ if (!builtinSnapshot) return;
2710
+ STYLE_TO_CHUNK.clear();
2711
+ for (const [style, chunk] of builtinSnapshot) STYLE_TO_CHUNK.set(style, chunk);
2712
+ }
2713
+ //#endregion
2714
+ //#region src/prop-handlers.ts
2240
2715
  /**
2241
- * OKHSL Plugin for Tasty.
2716
+ * Props middleware for tasty components.
2242
2717
  *
2243
- * Adds support for the `okhsl()` color function in tasty styles.
2718
+ * A prop handler receives a component's props and returns them, changed or not.
2719
+ * That makes it the extension point for props that are *not* style properties:
2720
+ * it can read a custom prop, strip it so it never reaches the DOM, and fold its
2721
+ * meaning into `styles`, `mods`, `tokens`, `variant`, or `as`.
2244
2722
  *
2245
- * @example
2246
2723
  * ```ts
2247
- * import { configure } from '@tenphi/tasty';
2248
- * import { okhslPlugin } from '@tenphi/tasty';
2249
- *
2250
2724
  * configure({
2251
- * plugins: [okhslPlugin()],
2252
- * });
2253
- *
2254
- * // Now you can use okhsl in styles:
2255
- * const Box = tasty({
2256
- * styles: {
2257
- * fill: 'okhsl(240 50% 50%)',
2725
+ * propHandlers: {
2726
+ * glaze: (props) => {
2727
+ * const { glaze, ...rest } = props;
2728
+ * if (!glaze) return rest;
2729
+ * return { ...rest, styles: mergeStyles(glazeStyles(glaze), rest.styles) };
2730
+ * },
2258
2731
  * },
2259
2732
  * });
2260
2733
  * ```
2734
+ *
2735
+ * Handlers run on every render of every tasty component, so the registry is a
2736
+ * pre-composed chain that is `null` while nothing is registered — the cost when
2737
+ * unused is one property load and one branch.
2261
2738
  */
2262
- const okhslPlugin = () => ({
2263
- name: "okhsl",
2264
- funcs: { okhsl: okhslFunc }
2739
+ const GTKEY$1 = "__tasty_prop_handlers__";
2740
+ const globalStore$1 = globalThis;
2741
+ const propHandlerRegistry = globalStore$1[GTKEY$1] ?? (globalStore$1[GTKEY$1] = {
2742
+ apply: null,
2743
+ list: []
2265
2744
  });
2266
- //#endregion
2267
- //#region src/plugins/okhst-plugin.ts
2745
+ function normalize(key, definition, options) {
2746
+ const source = options?.source ?? "configure()";
2747
+ if (typeof definition === "function") return {
2748
+ key,
2749
+ triggers: [key],
2750
+ fn: definition,
2751
+ source
2752
+ };
2753
+ if (Array.isArray(definition)) {
2754
+ const [first, fn] = definition;
2755
+ if (typeof fn !== "function") throw new Error(`[Tasty] Invalid prop handler definition for "${key}". Tuple must have a function as the second element: [string, function] or [string[], function].`);
2756
+ if (first === "*") return {
2757
+ key,
2758
+ triggers: null,
2759
+ fn,
2760
+ source
2761
+ };
2762
+ if (typeof first === "string") return {
2763
+ key,
2764
+ triggers: [first],
2765
+ fn,
2766
+ source
2767
+ };
2768
+ if (Array.isArray(first)) return {
2769
+ key,
2770
+ triggers: first.includes("*") ? null : first,
2771
+ fn,
2772
+ source
2773
+ };
2774
+ throw new Error(`[Tasty] Invalid prop handler definition for "${key}". First element must be a string or string array.`);
2775
+ }
2776
+ throw new Error(`[Tasty] Invalid prop handler definition for "${key}". Expected function, [string, function], or [string[], function].`);
2777
+ }
2268
2778
  /**
2269
- * OKHST Plugin for Tasty
2270
- *
2271
- * Converts OKHST color syntax to RGB notation.
2272
- * OKHST is OKHSL with the lightness axis replaced by a contrast-uniform tone axis.
2273
- * Supports angle units: deg, turn, rad, or unitless (degrees).
2779
+ * Run one handler, treating a nothing/non-object return as "unchanged".
2274
2780
  *
2275
- * Examples:
2276
- * okhst(240.5 50% 50%)
2277
- * okhst(240.5deg 50% 50%)
2278
- * okhst(0.25turn 50% 50%)
2279
- * okhst(1.57rad 50% 50%)
2781
+ * `isDevEnv()` is called lazily rather than captured at module load so the
2782
+ * warnings are assertable in tests; it is only reached on the misuse path.
2280
2783
  */
2784
+ function runOne(handler, props) {
2785
+ const next = handler.fn(props);
2786
+ if (next != null && typeof next === "object" && !Array.isArray(next)) return next;
2787
+ if (isDevEnv()) console.warn(next == null ? `[Tasty] propHandlers["${handler.key}"] (from ${handler.source}) returned ${String(next)}. Props are treated as unchanged — did you forget to \`return props\`?` : `[Tasty] propHandlers["${handler.key}"] (from ${handler.source}) returned ${Array.isArray(next) ? "an array" : typeof next}. A prop handler must return a props object. The result was ignored.`);
2788
+ return props;
2789
+ }
2790
+ function compose(list) {
2791
+ if (list.length === 0) return null;
2792
+ if (list.length === 1) {
2793
+ const only = list[0];
2794
+ if (only.triggers === null) return (props) => runOne(only, props);
2795
+ if (only.triggers.length === 1) {
2796
+ const trigger = only.triggers[0];
2797
+ return (props) => trigger in props ? runOne(only, props) : props;
2798
+ }
2799
+ }
2800
+ return (props) => {
2801
+ for (const handler of list) {
2802
+ const triggers = handler.triggers;
2803
+ if (triggers !== null) {
2804
+ let triggered = false;
2805
+ for (const trigger of triggers) if (trigger in props) {
2806
+ triggered = true;
2807
+ break;
2808
+ }
2809
+ if (!triggered) continue;
2810
+ }
2811
+ props = runOne(handler, props);
2812
+ }
2813
+ return props;
2814
+ };
2815
+ }
2281
2816
  /**
2282
- * The okhst function handler for tasty parser.
2283
- * Receives parsed style groups and returns an RGB color string.
2817
+ * Register a props middleware under `key`, replacing any handler already
2818
+ * registered under the same key while keeping its position in the chain.
2284
2819
  */
2285
- const okhstFunc = createColorFunc("okhst", "H S T", okhstToSrgb);
2820
+ function registerPropHandler(key, definition, options) {
2821
+ const normalized = normalize(key, definition, options);
2822
+ const { list } = propHandlerRegistry;
2823
+ const existing = list.findIndex((handler) => handler.key === key);
2824
+ if (existing === -1) list.push(normalized);
2825
+ else list[existing] = normalized;
2826
+ propHandlerRegistry.apply = compose(list);
2827
+ }
2828
+ /** Drop every registered props middleware. Called by `resetConfig()`. */
2829
+ function resetPropHandlers() {
2830
+ if (propHandlerRegistry.list.length === 0) return;
2831
+ propHandlerRegistry.list.length = 0;
2832
+ propHandlerRegistry.apply = null;
2833
+ }
2834
+ //#endregion
2835
+ //#region src/properties/index.ts
2836
+ const PROPERTIES_KEY = "@property";
2286
2837
  /**
2287
- * OKHST Plugin for Tasty.
2838
+ * Valid CSS custom property name pattern (after the -- prefix).
2839
+ * Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.
2840
+ */
2841
+ const VALID_PROPERTY_NAME_PATTERN = /^[a-z_][a-z0-9-_]*$/i;
2842
+ /**
2843
+ * Validate a CSS custom property name (the part after --).
2844
+ * Returns true if the name is valid for use as a CSS custom property.
2845
+ */
2846
+ function isValidPropertyName(name) {
2847
+ return VALID_PROPERTY_NAME_PATTERN.test(name);
2848
+ }
2849
+ /**
2850
+ * Check if styles object has local @property definition.
2851
+ * Fast path: single property lookup.
2852
+ */
2853
+ function hasLocalProperties(styles) {
2854
+ return PROPERTIES_KEY in styles;
2855
+ }
2856
+ /**
2857
+ * Extract local @property from styles object.
2858
+ * Returns null if no local properties (fast path).
2859
+ */
2860
+ function extractLocalProperties(styles) {
2861
+ const properties = styles[PROPERTIES_KEY];
2862
+ if (!properties || typeof properties !== "object") return null;
2863
+ return properties;
2864
+ }
2865
+ /**
2866
+ * Parse a property token name and return the CSS property name and whether it's a color.
2867
+ * Supports tasty token syntax and validates the property name.
2288
2868
  *
2289
- * Adds support for the `okhst()` color function in tasty styles.
2869
+ * Token formats:
2870
+ * - `$name` → { cssName: '--name', isColor: false }
2871
+ * - `#name` → { cssName: '--name-color', isColor: true }
2872
+ * - `--name` → { cssName: '--name', isColor: false } (legacy, auto-detect color by suffix)
2873
+ * - `name` → { cssName: '--name', isColor: false } (legacy)
2290
2874
  *
2291
- * @example
2292
- * ```ts
2293
- * import { configure } from '@tenphi/tasty';
2294
- * import { okhstPlugin } from '@tenphi/tasty';
2875
+ * @param token - The property token to parse
2876
+ * @returns Parsed result with cssName, isColor, isValid, and optional error
2877
+ */
2878
+ function parsePropertyToken(token) {
2879
+ if (!token || typeof token !== "string") return {
2880
+ cssName: "",
2881
+ isColor: false,
2882
+ isValid: false,
2883
+ error: "Property token must be a non-empty string"
2884
+ };
2885
+ let name;
2886
+ let isColor;
2887
+ if (token.startsWith("$")) {
2888
+ name = token.slice(1);
2889
+ isColor = false;
2890
+ } else if (token.startsWith("#")) {
2891
+ name = token.slice(1);
2892
+ isColor = true;
2893
+ } else if (token.startsWith("--")) {
2894
+ name = token.slice(2);
2895
+ isColor = token.endsWith("-color");
2896
+ } else {
2897
+ name = token;
2898
+ isColor = token.endsWith("-color");
2899
+ }
2900
+ if (!name) return {
2901
+ cssName: "",
2902
+ isColor,
2903
+ isValid: false,
2904
+ error: "Property name cannot be empty"
2905
+ };
2906
+ if (!isValidPropertyName(name)) return {
2907
+ cssName: "",
2908
+ isColor,
2909
+ isValid: false,
2910
+ error: `Invalid property name "${name}". Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.`
2911
+ };
2912
+ let cssName;
2913
+ if (token.startsWith("#")) cssName = `--${name}-color`;
2914
+ else cssName = `--${name}`;
2915
+ return {
2916
+ cssName,
2917
+ isColor,
2918
+ isValid: true
2919
+ };
2920
+ }
2921
+ /**
2922
+ * Normalize a property definition to a consistent string representation.
2923
+ * Used for comparing definitions to detect type conflicts.
2295
2924
  *
2296
- * configure({
2297
- * plugins: [okhstPlugin()],
2298
- * });
2925
+ * Only `syntax` and `inherits` are compared — `initialValue` is intentionally
2926
+ * excluded because different components may set different defaults for the
2927
+ * same typed property (e.g. auto-inferred `0px` vs explicit `6px`).
2299
2928
  *
2300
- * // Now you can use okhst in styles:
2301
- * const Box = tasty({
2302
- * styles: {
2303
- * fill: 'okhst(240 50% 50%)',
2304
- * },
2305
- * });
2306
- * ```
2929
+ * Keys are sorted alphabetically to ensure consistent comparison:
2930
+ * { inherits: true, syntax: '<color>' } === { syntax: '<color>', inherits: true }
2307
2931
  */
2308
- const okhstPlugin = () => ({
2309
- name: "okhst",
2310
- funcs: { okhst: okhstFunc }
2311
- });
2312
- //#endregion
2313
- //#region src/utils/styles.ts
2932
+ function normalizePropertyDefinition(def) {
2933
+ const normalized = {};
2934
+ if (def.inherits !== void 0) normalized.inherits = def.inherits;
2935
+ if (def.syntax !== void 0) normalized.syntax = def.syntax;
2936
+ return JSON.stringify(normalized);
2937
+ }
2938
+ /**
2939
+ * Get the effective property definition for a token.
2940
+ * For color tokens (#name), auto-sets syntax to '<color>' and defaults initialValue to 'transparent'.
2941
+ *
2942
+ * @param token - Property token ($name, #name, --name, or plain name)
2943
+ * @param userDefinition - User-provided definition options
2944
+ * @returns Effective definition with cssName, definition, isValid, and optional error
2945
+ */
2946
+ function getEffectiveDefinition(token, userDefinition) {
2947
+ const parsed = parsePropertyToken(token);
2948
+ if (!parsed.isValid) return {
2949
+ cssName: "",
2950
+ definition: userDefinition,
2951
+ isColor: false,
2952
+ isValid: false,
2953
+ error: parsed.error
2954
+ };
2955
+ if (parsed.isColor) return {
2956
+ cssName: parsed.cssName,
2957
+ definition: {
2958
+ syntax: "<color>",
2959
+ inherits: userDefinition.inherits,
2960
+ initialValue: userDefinition.initialValue ?? "transparent"
2961
+ },
2962
+ isColor: true,
2963
+ isValid: true
2964
+ };
2965
+ return {
2966
+ cssName: parsed.cssName,
2967
+ definition: userDefinition,
2968
+ isColor: false,
2969
+ isValid: true
2970
+ };
2971
+ }
2972
+ const UNIT_TO_SYNTAX = {};
2973
+ const LENGTH_UNITS = [
2974
+ "px",
2975
+ "em",
2976
+ "rem",
2977
+ "vw",
2978
+ "vh",
2979
+ "vmin",
2980
+ "vmax",
2981
+ "ch",
2982
+ "ex",
2983
+ "cap",
2984
+ "ic",
2985
+ "lh",
2986
+ "rlh",
2987
+ "svw",
2988
+ "svh",
2989
+ "lvw",
2990
+ "lvh",
2991
+ "dvw",
2992
+ "dvh",
2993
+ "cqw",
2994
+ "cqh",
2995
+ "cqi",
2996
+ "cqb",
2997
+ "cqmin",
2998
+ "cqmax"
2999
+ ];
3000
+ const ANGLE_UNITS = [
3001
+ "deg",
3002
+ "rad",
3003
+ "grad",
3004
+ "turn"
3005
+ ];
3006
+ const TIME_UNITS = ["ms", "s"];
3007
+ for (const u of LENGTH_UNITS) UNIT_TO_SYNTAX[u] = {
3008
+ syntax: "<length-percentage>",
3009
+ initialValue: "0px"
3010
+ };
3011
+ UNIT_TO_SYNTAX["%"] = {
3012
+ syntax: "<length-percentage>",
3013
+ initialValue: "0px"
3014
+ };
3015
+ for (const u of ANGLE_UNITS) UNIT_TO_SYNTAX[u] = {
3016
+ syntax: "<angle>",
3017
+ initialValue: "0deg"
3018
+ };
3019
+ for (const u of TIME_UNITS) UNIT_TO_SYNTAX[u] = {
3020
+ syntax: "<time>",
3021
+ initialValue: "0s"
3022
+ };
2314
3023
  /**
2315
- * Normalize a color token value.
2316
- * - Boolean `true` is converted to `'transparent'`
2317
- * - Boolean `false` returns `null` (signals the token should be skipped)
2318
- * - Other values are returned as-is
3024
+ * Infer CSS @property syntax from a concrete value.
3025
+ * Detects numeric types: \<number\>, \<length-percentage\>, \<angle\>, \<time\>.
3026
+ * Length and percentage values both map to \<length-percentage\> for maximum flexibility.
3027
+ * Color properties are handled separately via the #name token convention
3028
+ * (--name-color gets \<color\> syntax automatically in getEffectiveDefinition).
2319
3029
  *
2320
- * @param value - The raw token value
2321
- * @returns Normalized value or null if the token should be skipped
3030
+ * @param value - The CSS value to infer from (e.g. '10px', '1', '45deg')
3031
+ * @returns Inferred syntax and initial value, or null if not inferable
2322
3032
  */
2323
- function normalizeColorTokenValue(value) {
2324
- if (value === true) return "transparent";
2325
- if (value === false) return null;
2326
- return value;
2327
- }
2328
- const COLOR_VAR_PATTERN = /var\(--([a-z0-9-]+)-color/;
2329
- const COLOR_VAR_COMPONENTS_PATTERN = /var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;
2330
- const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
2331
- const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
2332
- const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
2333
- function isSimpleColorFast(val) {
2334
- switch (val.charCodeAt(0)) {
2335
- case 35: return RE_HEX_COLOR.test(val);
2336
- case 114: return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98;
2337
- case 104: return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108;
2338
- case 108: return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104;
2339
- case 111: return val.startsWith("oklch(") || val.startsWith("okhsl(") || val.startsWith("okhst(");
2340
- case 118: return RE_VAR_COLOR.test(val);
2341
- case 99: return val === "currentColor" || val === "currentcolor";
2342
- case 116: return val === "transparent";
2343
- default: return getNamedColorHex().has(val.toLowerCase());
2344
- }
2345
- }
2346
- let colorWarningCount = 0;
2347
- const MAX_COLOR_WARNINGS = 10;
2348
- const CUSTOM_UNITS = {
2349
- r: "6px",
2350
- cr: "10px",
2351
- bw: "1px",
2352
- ow: "3px",
2353
- x: "8px",
2354
- sf: function sf(num) {
2355
- return `minmax(0, ${num}fr)`;
3033
+ function inferSyntaxFromValue(value) {
3034
+ if (!value || typeof value !== "string") return null;
3035
+ const trimmed = value.trim();
3036
+ if (!trimmed) return null;
3037
+ if (RE_NUMBER.test(trimmed)) {
3038
+ if (parseFloat(trimmed) === 0) return null;
3039
+ return {
3040
+ syntax: "<number>",
3041
+ initialValue: "0"
3042
+ };
2356
3043
  }
2357
- };
2358
- const DIRECTIONS = [
2359
- "top",
2360
- "right",
2361
- "bottom",
2362
- "left"
2363
- ];
2364
- let __tastyParser = null;
2365
- function getOrCreateParser() {
2366
- if (!__tastyParser) {
2367
- __tastyParser = new StyleParser({ units: CUSTOM_UNITS });
2368
- __tastyParser.setFuncs(__tastyFuncs);
3044
+ const unitMatch = trimmed.match(RE_RAW_UNIT);
3045
+ if (unitMatch) {
3046
+ const mapping = UNIT_TO_SYNTAX[unitMatch[2]];
3047
+ if (mapping) return mapping;
2369
3048
  }
2370
- return __tastyParser;
2371
- }
2372
- const __tastyFuncs = {
2373
- okhsl: okhslFunc,
2374
- okhst: okhstFunc
2375
- };
2376
- function customFunc(name, fn) {
2377
- __tastyFuncs[name] = fn;
2378
- getOrCreateParser().setFuncs(__tastyFuncs);
3049
+ return null;
2379
3050
  }
3051
+ //#endregion
3052
+ //#region src/utils/hash.ts
2380
3053
  /**
2381
- * Get the global StyleParser instance.
2382
- * Used by configure() to apply parser configuration.
3054
+ * Fast string hash (djb2) for deduplication keys.
3055
+ * Not cryptographic just collision-resistant enough for cache keys.
2383
3056
  */
2384
- function getGlobalParser() {
2385
- return getOrCreateParser();
3057
+ function hashString(str) {
3058
+ let hash = 5381;
3059
+ for (let i = 0; i < str.length; i++) hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
3060
+ return (hash >>> 0).toString(36);
2386
3061
  }
3062
+ //#endregion
3063
+ //#region src/utils/name-prefix.ts
2387
3064
  /**
2388
- * Get the current custom functions registry.
2389
- * Used by configure() to merge with new functions.
3065
+ * Name prefix utilities for generated identifiers.
3066
+ *
3067
+ * Tasty generates three kinds of identifiers from content hashes:
3068
+ * - class names (used in DOM `class` attribute)
3069
+ * - keyframe names (used in CSS `animation`)
3070
+ * - counter-style names (used in CSS `list-style-type`)
3071
+ *
3072
+ * All three derive from a single configurable prefix so that an app
3073
+ * can namespace every identifier under one string. Discriminator letters
3074
+ * (`k`, `c`) keep the three kinds visually distinct in devtools — they
3075
+ * are not required for correctness (CSS keeps these in separate
3076
+ * namespaces), only for readability.
3077
+ *
3078
+ * The runtime / SSR / RSC paths must agree on the prefix; otherwise the
3079
+ * client-side hash for a given style will not match the server-rendered
3080
+ * class and hydration breaks. The zero-runtime build path uses a
3081
+ * different default (`'ts'`) so its classes can't collide with runtime
3082
+ * (`'t'`) classes when both are loaded on the same page.
2390
3083
  */
2391
- function getGlobalFuncs() {
2392
- return __tastyFuncs;
2393
- }
3084
+ /** Default prefix used by the runtime / SSR / RSC paths. */
3085
+ const DEFAULT_NAME_PREFIX = "t";
3086
+ /** Default prefix used by the zero-runtime (`tastyStatic`) build path. */
3087
+ const DEFAULT_ZERO_NAME_PREFIX = "ts";
2394
3088
  /**
2395
- * Storage for predefined tokens that are replaced during style parsing.
2396
- * Keys are token names (with $ or # prefix), values are pre-processed CSS values.
3089
+ * Allowed shape: starts with a letter or underscore, then letters/
3090
+ * digits/underscore/hyphen. Length capped at 32 to keep generated
3091
+ * names sane. Matches the CSS identifier rules for the common case
3092
+ * while keeping the surface conservative.
2397
3093
  */
2398
- let __globalPredefinedTokens = null;
3094
+ const NAME_PREFIX_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$/;
2399
3095
  /**
2400
- * Set global predefined tokens.
2401
- * Called from configure() after processing token values.
2402
- * Merges with existing tokens (new tokens override existing ones with same key).
2403
- * Keys are normalized to lowercase (parser lowercases input before classification).
2404
- * @internal
3096
+ * Validate a `namePrefix` value.
3097
+ * Throws a TypeError with a descriptive message on invalid input so
3098
+ * misconfiguration fails loudly at `configure()` time rather than
3099
+ * surfacing later as broken hydration.
2405
3100
  */
2406
- function setGlobalPredefinedTokens(tokens) {
2407
- const normalizedTokens = {};
2408
- for (const [key, value] of Object.entries(tokens)) {
2409
- const lowerKey = key.toLowerCase();
2410
- const lowerValue = value.toLowerCase();
2411
- if (lowerKey.startsWith("#") && lowerValue === "#current") {
2412
- 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.`);
2413
- continue;
2414
- }
2415
- normalizedTokens[lowerKey] = value;
2416
- }
2417
- __globalPredefinedTokens = __globalPredefinedTokens ? {
2418
- ...__globalPredefinedTokens,
2419
- ...normalizedTokens
2420
- } : normalizedTokens;
2421
- getOrCreateParser().clearCache();
3101
+ function validateNamePrefix(prefix) {
3102
+ if (typeof prefix !== "string") throw new TypeError(`[Tasty] namePrefix must be a string, got ${typeof prefix}.`);
3103
+ if (!NAME_PREFIX_PATTERN.test(prefix)) throw new TypeError(`[Tasty] namePrefix "${prefix}" is invalid. It must start with a letter (a-z, A-Z) or "_", contain only letters, digits, "_" or "-", and be 1-32 characters long. Examples: "t", "ts", "myapp-", "_foo".`);
2422
3104
  }
2423
3105
  /**
2424
- * Get the current global predefined tokens.
2425
- * Returns null if no tokens are configured.
3106
+ * Build a class name: `${prefix}${hash}`.
3107
+ * The hash is appended verbatim supply a separator inside the prefix
3108
+ * itself if you want one (e.g. `'myapp-'`).
2426
3109
  */
2427
- function getGlobalPredefinedTokens() {
2428
- return __globalPredefinedTokens;
3110
+ function makeClassName(prefix, hash) {
3111
+ return `${prefix}${hash}`;
2429
3112
  }
2430
3113
  /**
2431
- * Reset global predefined tokens.
2432
- * Used for testing.
2433
- * @internal
3114
+ * Build a keyframe name: `${prefix}k${suffix}`.
3115
+ * The `k` discriminator keeps keyframe names visually distinct from
3116
+ * class names sharing the same prefix. `suffix` is typically a content
3117
+ * hash but may be a counter for ad-hoc allocation.
2434
3118
  */
2435
- function resetGlobalPredefinedTokens() {
2436
- __globalPredefinedTokens = null;
2437
- getOrCreateParser().clearCache();
3119
+ function makeKeyframeName(prefix, suffix) {
3120
+ return `${prefix}k${suffix}`;
2438
3121
  }
2439
3122
  /**
2440
- *
2441
- * @param {String} value
2442
- * @param {Number} mode
2443
- * @returns {Object<String,String|Array>}
3123
+ * Build a counter-style name: `${prefix}c${suffix}`.
3124
+ * The `c` discriminator keeps counter-style names visually distinct
3125
+ * from class names sharing the same prefix.
2444
3126
  */
2445
- function parseStyle(value) {
2446
- let str;
2447
- if (typeof value === "string") str = value;
2448
- else if (typeof value === "number") str = String(value);
2449
- else str = "";
2450
- return getOrCreateParser().process(str);
3127
+ function makeCounterStyleName(prefix, suffix) {
3128
+ return `${prefix}c${suffix}`;
3129
+ }
3130
+ /** Escape a string for safe inclusion in a regex literal. */
3131
+ function escapeRegex(str) {
3132
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2451
3133
  }
2452
3134
  /**
2453
- * Parse color. Find it value, name and opacity.
2454
- * Optimized to avoid heavy parseStyle calls for simple color patterns.
3135
+ * Regex matching any tasty class for the given prefix.
3136
+ * Used by the runtime GC's DOM scan and class-allocation bookkeeping.
2455
3137
  */
2456
- function parseColor(val, ignoreError = false) {
2457
- if (typeof val !== "string") val = String(val ?? "");
2458
- val = val.trim();
2459
- if (!val) return {};
2460
- const isSimpleColor = isSimpleColorFast(val);
2461
- let firstColor;
2462
- if (isSimpleColor) firstColor = val;
2463
- else {
2464
- const extractedColor = parseStyle(val).groups.find((g) => g.colors.length)?.colors[0];
2465
- if (!extractedColor) {
2466
- if (!ignoreError && colorWarningCount < MAX_COLOR_WARNINGS) {
2467
- console.warn("[Tasty] unable to parse color:", val);
2468
- colorWarningCount++;
2469
- if (colorWarningCount === MAX_COLOR_WARNINGS) console.warn("[Tasty] color parsing warnings will be suppressed from now on");
2470
- }
2471
- return {};
2472
- }
2473
- firstColor = extractedColor;
2474
- }
2475
- let nameMatch = firstColor.match(COLOR_VAR_PATTERN);
2476
- if (!nameMatch) nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2477
- let opacity;
2478
- if (firstColor.startsWith("rgb") || firstColor.startsWith("hsl") || firstColor.startsWith("lch") || firstColor.startsWith("oklch") || firstColor.startsWith("okhsl")) {
2479
- const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);
2480
- if (alphaMatch) {
2481
- const v = parseFloat(alphaMatch[1]);
2482
- if (!isNaN(v)) opacity = v * 100;
2483
- }
2484
- }
2485
- return {
2486
- color: firstColor,
2487
- name: nameMatch ? nameMatch[1] : void 0,
2488
- opacity
2489
- };
2490
- }
2491
- function filterMods(mods, allowedMods) {
2492
- return mods.filter((mod) => allowedMods.includes(mod));
3138
+ function tastyClassRegex(prefix) {
3139
+ return new RegExp(`^${escapeRegex(prefix)}[a-z0-9]+$`);
2493
3140
  }
2494
- const _innerCache = /* @__PURE__ */ new WeakMap();
2495
- const _topLevelCache = /* @__PURE__ */ new WeakMap();
2496
- function stringifyStyles(styles) {
2497
- if (styles == null || typeof styles !== "object") return "";
2498
- const cached = _topLevelCache.get(styles);
2499
- if (cached !== void 0) return cached;
2500
- const obj = styles;
2501
- const keys = Object.keys(obj).sort();
2502
- const parts = [];
2503
- for (const k of keys) {
2504
- const v = obj[k];
2505
- if (v === void 0 || typeof v === "function" || typeof v === "symbol") continue;
2506
- const c0 = k.charCodeAt(0);
2507
- const needsInnerSort = (c0 >= 65 && c0 <= 90 || c0 === 38) && v && typeof v === "object" && !Array.isArray(v);
2508
- let sv;
2509
- if (needsInnerSort) {
2510
- sv = _innerCache.get(v);
2511
- if (sv === void 0) {
2512
- const innerObj = v;
2513
- const innerKeys = Object.keys(innerObj).sort();
2514
- const innerParts = [];
2515
- for (const ik of innerKeys) {
2516
- const ivs = JSON.stringify(innerObj[ik]);
2517
- if (ivs !== void 0) innerParts.push(JSON.stringify(ik) + ":" + ivs);
2518
- }
2519
- sv = "{" + innerParts.join(",") + "}";
2520
- _innerCache.set(v, sv);
2521
- }
2522
- } else sv = JSON.stringify(v);
2523
- parts.push(JSON.stringify(k) + ":" + sv);
2524
- }
2525
- const result = "{" + parts.join(",") + "}";
2526
- _topLevelCache.set(styles, result);
2527
- return result;
3141
+ /**
3142
+ * Global regex extracting tasty class names from RSC-inlined CSS.
3143
+ * Looks for the doubled-specificity pattern `.cls.cls` that
3144
+ * `formatRules()` always emits, which makes extraction reliable.
3145
+ */
3146
+ function rscClassRegexGlobal(prefix) {
3147
+ return new RegExp(`\\.(${escapeRegex(prefix)}[a-z0-9]+)\\.\\1`, "g");
2528
3148
  }
2529
3149
  //#endregion
2530
3150
  //#region src/properties/property-type-resolver.ts
@@ -2639,6 +3259,52 @@ function extractCSSWideKeyword(group) {
2639
3259
  if (group.values.length !== 1 || group.colors.length > 0) return null;
2640
3260
  return CSS_WIDE_KEYWORDS.has(group.values[0]) ? group.values[0] : null;
2641
3261
  }
3262
+ /** Warning keys already emitted, so each distinct offending value warns once. */
3263
+ const emittedWarnings$2 = /* @__PURE__ */ new Set();
3264
+ /**
3265
+ * Emit a style-level warning at most once per `key`. No-op outside dev mode.
3266
+ *
3267
+ * Style handlers run once per state combination and their results are cached,
3268
+ * but the same value can arrive from many components, so dedupe is required.
3269
+ *
3270
+ * `isDevEnv()` is called lazily rather than captured at module load (the pattern
3271
+ * used in `config.ts` and `states/index.ts`) so that tests can enable it with
3272
+ * `vi.stubEnv('NODE_ENV', 'development')` — `isDevEnv()` returns `false` for
3273
+ * `NODE_ENV=test`, which is why every module-load-gated warning in this repo is
3274
+ * currently untestable. The key is registered *before* the dev check, so
3275
+ * production pays at most one `isDevEnv()` call per distinct offending value and
3276
+ * never touches the console.
3277
+ */
3278
+ function warnOnceDev(key, message) {
3279
+ if (emittedWarnings$2.has(key)) return;
3280
+ emittedWarnings$2.add(key);
3281
+ if (!isDevEnv()) return;
3282
+ console.warn(`[Tasty] ${message}`);
3283
+ }
3284
+ /** Forget emitted style warnings. Called by `resetConfig()`; test isolation. */
3285
+ function resetStyleWarnings() {
3286
+ emittedWarnings$2.clear();
3287
+ }
3288
+ /**
3289
+ * Report a comma group that names direction modifiers together with more values
3290
+ * than the group can use.
3291
+ *
3292
+ * The parser buckets values and modifiers into separate arrays per comma group,
3293
+ * so `padding: '2x 4x top right'`, `padding: '2x top 4x right'` and
3294
+ * `padding: 'top 2x right 4x'` all reach a handler identically — the pairing a
3295
+ * reader infers from source order does not survive parsing. Such a group
3296
+ * therefore carries a single value, applied to every direction it names.
3297
+ *
3298
+ * `maxValues` is 2 only for a span modifier (`inset: '2x 4x bottom dock'`),
3299
+ * where the second value insets the sides the dock spans.
3300
+ *
3301
+ * Callers must guard on the count themselves so the valid path costs one integer
3302
+ * comparison; everything expensive happens here, on the misuse path only.
3303
+ */
3304
+ function warnExtraGroupValues(property, input, maxValues) {
3305
+ const message = maxValues === 2 ? `${property}="${input}": with a span modifier a group takes at most two values — one for the named edge and one for the spanned sides. The extra values are ignored.` : `${property}="${input}": a group that names directions takes a single value, applied to every direction it names. The extra values are ignored — use comma-separated groups instead, e.g. ${property}="2x top, 4x right".`;
3306
+ warnOnceDev(`extra-values:${property}:${input}`, message);
3307
+ }
2642
3308
  //#endregion
2643
3309
  //#region src/styles/border.ts
2644
3310
  /**
@@ -2934,8 +3600,9 @@ const DEFAULT_OPAQUE_COLOR = "rgb(0 0 0 / 1)";
2934
3600
  */
2935
3601
  function processGroup(group, isOnlyGroup) {
2936
3602
  let { values } = group;
2937
- const { mods, colors } = group;
2938
- let directions = filterMods(mods, DIRECTIONS);
3603
+ const { input, mods, colors } = group;
3604
+ const named = filterMods(mods, DIRECTIONS);
3605
+ let directions = named;
2939
3606
  if (!values.length) values = ["calc(2 * var(--gap))"];
2940
3607
  if (!directions.length) if (isOnlyGroup) directions = [
2941
3608
  "top",
@@ -2946,8 +3613,9 @@ function processGroup(group, isOnlyGroup) {
2946
3613
  else return [];
2947
3614
  const transparentColor = colors?.[0] || DEFAULT_TRANSPARENT_COLOR;
2948
3615
  const opaqueColor = colors?.[1] || DEFAULT_OPAQUE_COLOR;
3616
+ if (named.length > 0 && values.length > 1) warnExtraGroupValues("fade", input, 1);
2949
3617
  return directions.map((direction, index) => {
2950
- const size = values[index] || values[index % 2] || values[0];
3618
+ const size = named.length > 0 ? values[0] : values[index] || values[index % 2] || values[0];
2951
3619
  return `linear-gradient(${DIRECTION_MAP[direction]}, ${transparentColor} 0%, ${opaqueColor} ${size})`;
2952
3620
  });
2953
3621
  }
@@ -2963,6 +3631,7 @@ function fadeStyle({ fade }) {
2963
3631
  const gradients = [];
2964
3632
  for (const group of groups) {
2965
3633
  const groupGradients = processGroup({
3634
+ input: group.input ?? "",
2966
3635
  values: group.values ?? [],
2967
3636
  mods: group.mods ?? [],
2968
3637
  colors: group.colors ?? []
@@ -3193,12 +3862,15 @@ function parseSingleValue(val, defaultValue, trueValue) {
3193
3862
  const { values } = parseStyle(strVal).groups[0] ?? { values: [] };
3194
3863
  return values[0] || defaultValue;
3195
3864
  }
3196
- function extractGroupData(group, defaultValue, spanModifiers) {
3865
+ function extractGroupData(group, property, defaultValue, spanModifiers) {
3197
3866
  const { values = [], mods = [] } = group;
3867
+ const directions = filterMods(mods, DIRECTIONS);
3868
+ const span = !!spanModifiers?.some((mod) => mods.includes(mod));
3869
+ if (directions.length > 0 && values.length > (span ? 2 : 1)) warnExtraGroupValues(property, group.input, span ? 2 : 1);
3198
3870
  return {
3199
3871
  values: values.length ? values : [defaultValue],
3200
- directions: filterMods(mods, DIRECTIONS),
3201
- span: !!spanModifiers?.some((mod) => mods.includes(mod))
3872
+ directions,
3873
+ span
3202
3874
  };
3203
3875
  }
3204
3876
  function applyGroup(dirs, values, directions, span = false) {
@@ -3208,18 +3880,18 @@ function applyGroup(dirs, values, directions, span = false) {
3208
3880
  dirs.right = values[1] || values[0];
3209
3881
  dirs.bottom = values[2] || values[0];
3210
3882
  dirs.left = values[3] || values[1] || values[0];
3211
- } else {
3212
- const spanValue = span ? values[directions.length] : void 0;
3213
- if (span) directions.forEach((dir, i) => {
3214
- const perpendicular = spanValue ?? values[i] ?? values[0];
3883
+ return;
3884
+ }
3885
+ const value = values[0];
3886
+ if (span) {
3887
+ const spanValue = values[1] ?? value;
3888
+ for (const dir of directions) {
3215
3889
  const d = DIRECTIONS.indexOf(dir);
3216
- dirs[DIRECTIONS[(d + 1) % 4]] = perpendicular;
3217
- dirs[DIRECTIONS[(d + 3) % 4]] = perpendicular;
3218
- });
3219
- directions.forEach((dir, i) => {
3220
- dirs[dir] = values[i] ?? values[0];
3221
- });
3890
+ dirs[DIRECTIONS[(d + 1) % 4]] = spanValue;
3891
+ dirs[DIRECTIONS[(d + 3) % 4]] = spanValue;
3892
+ }
3222
3893
  }
3894
+ for (const dir of directions) dirs[dir] = value;
3223
3895
  }
3224
3896
  function optimizeShorthand(property, dirs) {
3225
3897
  const { top, right, bottom, left } = dirs;
@@ -3280,7 +3952,7 @@ function processDirectionalStyle(config, props) {
3280
3952
  if (groupDirs.length === 0) dirs.top = dirs.right = dirs.bottom = dirs.left = kw;
3281
3953
  else for (const dir of groupDirs) dirs[dir] = kw;
3282
3954
  } else {
3283
- const { values, directions, span } = extractGroupData(group, defaultValue, spanModifiers);
3955
+ const { values, directions, span } = extractGroupData(group, property, defaultValue, spanModifiers);
3284
3956
  applyGroup(dirs, values, directions, span);
3285
3957
  }
3286
3958
  }
@@ -3786,13 +4458,6 @@ const PREFIX = "[Tasty]";
3786
4458
  function warn(...args) {
3787
4459
  console.warn(PREFIX, ...args);
3788
4460
  }
3789
- function deprecationWarning(condition, { property, name, betterAlternative, reason }) {
3790
- if (condition) return;
3791
- console.group(`⚠️ ${PREFIX} Deprecation in ${name}`);
3792
- warn(`"${property}" is deprecated, consider better alternative: ${typeof betterAlternative === "function" ? betterAlternative() : betterAlternative}`);
3793
- if (reason) warn(`Reason: ${typeof reason === "function" ? reason() : reason}`);
3794
- console.groupEnd();
3795
- }
3796
4461
  //#endregion
3797
4462
  //#region src/styles/scrollbar.ts
3798
4463
  /**
@@ -4028,6 +4693,16 @@ widthStyle.__lookupStyles = [
4028
4693
  //#endregion
4029
4694
  //#region src/styles/predefined.ts
4030
4695
  const devMode$2 = isDevEnv();
4696
+ /**
4697
+ * Dev-mode check evaluated per call rather than at module load.
4698
+ *
4699
+ * `isDevEnv()` reports `false` for `NODE_ENV=test`, so a module-load capture makes
4700
+ * a warning impossible to assert in a test. Only used on paths that already know
4701
+ * something is wrong, so the extra call is never on the hot path.
4702
+ */
4703
+ function inDevMode() {
4704
+ return isDevEnv();
4705
+ }
4031
4706
  const columnsConverter = (val) => {
4032
4707
  if (typeof val === "number") return "minmax(1px, 1fr) ".repeat(val).trim();
4033
4708
  };
@@ -4144,6 +4819,63 @@ function validateHandler(name, handler, lookupStyles) {
4144
4819
  if (typeof handler !== "function") console.warn(`[Tasty] Handler "${name}" is not a function. Handlers must be functions that return CSSMap, CSSMap[], or null.`);
4145
4820
  if (!lookupStyles || !Array.isArray(lookupStyles) || lookupStyles.length === 0) console.warn(`[Tasty] Handler "${name}" has invalid lookupStyles. Expected non-empty array of style names.`);
4146
4821
  }
4822
+ /** Describe a handler in a warning message. */
4823
+ function describeHandler(lookupStyles, options) {
4824
+ const name = options?.key ?? lookupStyles.join(", ");
4825
+ return options?.source ? `"${name}" (from ${options.source})` : `"${name}"`;
4826
+ }
4827
+ /**
4828
+ * Keep a handler's dependencies inside one chunk.
4829
+ *
4830
+ * Chunks are rendered and cached independently and a chunk's cache key covers
4831
+ * only its own style values, so a handler whose `__lookupStyles` straddle two
4832
+ * chunks gets invoked once per chunk with a *subset* of its inputs — and can emit
4833
+ * stale CSS. Custom style names are unknown to the built-in chunk lists and would
4834
+ * otherwise fall into `misc`, splitting any handler that mixes them with built-in
4835
+ * styles. Pull them into the built-in chunk instead.
4836
+ *
4837
+ * Runs in production too: this is a correctness fix, not a diagnostic. It only
4838
+ * ever moves names the built-in lists don't know, so no existing class name can
4839
+ * change.
4840
+ */
4841
+ function alignHandlerChunks(lookupStyles, options) {
4842
+ const known = /* @__PURE__ */ new Set();
4843
+ const unknown = [];
4844
+ for (const styleName of lookupStyles) {
4845
+ const chunk = STYLE_TO_CHUNK.get(styleName);
4846
+ if (chunk) known.add(chunk);
4847
+ else unknown.push(styleName);
4848
+ }
4849
+ if (known.size > 1) {
4850
+ if (inDevMode()) console.warn(`[Tasty] Handler ${describeHandler(lookupStyles, options)} depends on styles from different chunks (${[...known].sort().join(", ")}). Chunks are cached independently, so this handler will be invoked once per chunk with only part of its dependencies and can emit stale CSS. Split it into per-chunk handlers, or have one property expand into the others before chunking.`);
4851
+ return;
4852
+ }
4853
+ if (unknown.length === 0) return;
4854
+ const target = known.size === 1 ? [...known][0] : CHUNK_NAMES.MISC;
4855
+ for (const styleName of unknown) assignStyleChunk(styleName, target);
4856
+ }
4857
+ /**
4858
+ * Warn when replacing a handler takes unrelated style properties down with it.
4859
+ *
4860
+ * Built-in handlers are shared across several style names (`displayStyle` also
4861
+ * emits `hide`, `overflow`, `whiteSpace`, `textOverflow`), so registering a
4862
+ * handler for one of them unregisters the built-in from *all* of its names. The
4863
+ * displaced names don't go dark — the pipeline falls back to an auto-generated
4864
+ * CSS alias and writes it back into the map — so `hide: true` starts emitting a
4865
+ * literal `hide: true` declaration. Silent wrongness, which is worth a warning.
4866
+ *
4867
+ * Must run before the handlers are actually removed.
4868
+ */
4869
+ function warnAboutOrphanedStyles(lookupStyles, handlersToRemove, options) {
4870
+ const incoming = new Set(lookupStyles);
4871
+ const orphaned = /* @__PURE__ */ new Set();
4872
+ for (const oldHandler of handlersToRemove) for (const oldStyleName of oldHandler.__lookupStyles ?? []) {
4873
+ if (incoming.has(oldStyleName)) continue;
4874
+ if ((STYLE_HANDLER_MAP$1[oldStyleName] ?? []).filter((h) => !handlersToRemove.has(h)).length === 0) orphaned.add(oldStyleName);
4875
+ }
4876
+ if (orphaned.size === 0) return;
4877
+ console.warn(`[Tasty] Custom handler ${describeHandler(lookupStyles, options)} displaced built-in handlers that also handle: ${[...orphaned].sort().join(", ")}. Those properties now fall back to auto-generated CSS aliases, which is usually wrong (e.g. \`hide: true\` would emit a literal \`hide: true\` declaration). Either declare them in this handler's lookup styles and delegate via \`styleHandlers.*\`, or pick a narrower style name.`);
4878
+ }
4147
4879
  /**
4148
4880
  * Register a custom handler, replacing any existing handlers for the same lookup styles.
4149
4881
  * This is called by configure() to process user-defined handlers.
@@ -4152,18 +4884,24 @@ function validateHandler(name, handler, lookupStyles) {
4152
4884
  * is removed from ALL its lookup styles to prevent double-processing.
4153
4885
  * For example, if gapStyle handles ['display', 'flow', 'gap'] and a new handler
4154
4886
  * is registered for just ['gap'], gapStyle is removed from display and flow too.
4887
+ *
4888
+ * Note: this bypasses the `configure()` lock, so calling it after styles have
4889
+ * been generated changes the handlers for already-emitted class names. Prefer
4890
+ * `configure({ handlers })`, which is rejected after first render.
4155
4891
  */
4156
- function registerHandler(handler) {
4892
+ function registerHandler(handler, options) {
4157
4893
  const lookupStyles = handler.__lookupStyles;
4158
4894
  if (!lookupStyles || lookupStyles.length === 0) {
4159
4895
  if (devMode$2) console.warn("[Tasty] Cannot register handler without __lookupStyles property.");
4160
4896
  return;
4161
4897
  }
4898
+ alignHandlerChunks(lookupStyles, options);
4162
4899
  const handlersToRemove = /* @__PURE__ */ new Set();
4163
4900
  for (const styleName of lookupStyles) {
4164
4901
  const existing = STYLE_HANDLER_MAP$1[styleName];
4165
4902
  if (existing) for (const existingHandler of existing) handlersToRemove.add(existingHandler);
4166
4903
  }
4904
+ if (handlersToRemove.size > 0 && inDevMode()) warnAboutOrphanedStyles(lookupStyles, handlersToRemove, options);
4167
4905
  for (const oldHandler of handlersToRemove) {
4168
4906
  const oldLookupStyles = oldHandler.__lookupStyles;
4169
4907
  if (oldLookupStyles) for (const oldStyleName of oldLookupStyles) {
@@ -4238,6 +4976,38 @@ const styleHandlers = {
4238
4976
  width: wrapHandler(widthStyle)
4239
4977
  };
4240
4978
  //#endregion
4979
+ //#region src/styles/define-handler.ts
4980
+ /**
4981
+ * Declare a multi-dependency style handler with its dependency names inferred
4982
+ * into the handler's parameter type.
4983
+ *
4984
+ * Without it, the multi-dependency tuple form needs a hand-written parameter
4985
+ * annotation that can silently drift from the dependency list:
4986
+ *
4987
+ * ```ts
4988
+ * // Annotation and dependency list are maintained separately.
4989
+ * handlers: {
4990
+ * spacing: [['gap', 'padding'], ({ gap, padding }: { gap?: string; padding?: string }) => …],
4991
+ * }
4992
+ *
4993
+ * // Both come from the same array; a typo in the destructure is a type error.
4994
+ * handlers: {
4995
+ * spacing: defineHandler(['gap', 'padding'], ({ gap, padding }) => …),
4996
+ * }
4997
+ * ```
4998
+ *
4999
+ * Values arrive **state-resolved but unparsed** — the raw authored DSL string
5000
+ * (`'2x'`, `'#purple.5'`), so call `parseStyle()` / `parseColor()` as needed. The
5001
+ * result is a kebab-case CSS declaration map (or an array of them); the reserved
5002
+ * `$` key applies the declarations to a nested selector.
5003
+ *
5004
+ * All names must belong to the same chunk. Names the built-in chunk lists don't
5005
+ * know are pulled into their handler's chunk automatically at registration.
5006
+ */
5007
+ function defineHandler(deps, handler) {
5008
+ return [deps, handler];
5009
+ }
5010
+ //#endregion
4241
5011
  //#region src/styles/index.ts
4242
5012
  const { STYLE_HANDLER_MAP } = predefine();
4243
5013
  //#endregion
@@ -4275,6 +5045,31 @@ var SheetManager = class {
4275
5045
  return sheetInfo.sheet?.sheet ?? null;
4276
5046
  }
4277
5047
  /**
5048
+ * Record an inserted rule text at its rule index (text mode only).
5049
+ *
5050
+ * `textRules` mirrors the sheet's rule order. `textContent` cannot be edited
5051
+ * rule-by-rule the way CSSOM can, so keeping the texts is what makes
5052
+ * deletion possible at all in this mode.
5053
+ */
5054
+ trackTextRule(sheet, ruleIndex, ruleText) {
5055
+ if (!sheet.textMode) return;
5056
+ const rules = sheet.textRules ??= [];
5057
+ while (rules.length < ruleIndex) rules.push("");
5058
+ rules[ruleIndex] = ruleText;
5059
+ }
5060
+ /**
5061
+ * Remove rule indices from a text-mode sheet and rewrite the element's text.
5062
+ * Returns the indices that were actually removed.
5063
+ */
5064
+ deleteTextRules(sheet, indices) {
5065
+ const rules = sheet.textRules;
5066
+ if (!rules) return [];
5067
+ const removed = [...new Set(indices)].filter((idx) => idx >= 0 && idx < rules.length).sort((a, b) => b - a);
5068
+ for (const idx of removed) rules.splice(idx, 1);
5069
+ if (removed.length > 0 && sheet.sheet) sheet.sheet.textContent = rules.length ? "\n" + rules.join("\n") : "";
5070
+ return removed;
5071
+ }
5072
+ /**
4278
5073
  * Determine the injection mode for a root.
4279
5074
  * ShadowRoot uses adopted stylesheets when supported; Document uses <style> elements.
4280
5075
  */
@@ -4310,7 +5105,8 @@ var SheetManager = class {
4310
5105
  keyframesCounter: 0,
4311
5106
  injectedProperties: /* @__PURE__ */ new Map(),
4312
5107
  injectedFontFaces: /* @__PURE__ */ new Set(),
4313
- injectedCounterStyles: /* @__PURE__ */ new Set(),
5108
+ injectedCounterStyles: /* @__PURE__ */ new Map(),
5109
+ injectedFunctions: /* @__PURE__ */ new Map(),
4314
5110
  globalRules: /* @__PURE__ */ new Map(),
4315
5111
  propertyTypeResolver: new PropertyTypeResolver(),
4316
5112
  usageMap: /* @__PURE__ */ new Map(),
@@ -4356,10 +5152,14 @@ var SheetManager = class {
4356
5152
  registry.sheets.push(sheetInfo);
4357
5153
  return sheetInfo;
4358
5154
  }
5155
+ const sheet = this.createStyleElement(root);
5156
+ const textMode = this.config.forceTextInjection === true || sheet.sheet == null;
4359
5157
  const sheetInfo = {
4360
- sheet: this.createStyleElement(root),
5158
+ sheet,
4361
5159
  ruleCount: 0,
4362
- holes: []
5160
+ holes: [],
5161
+ textMode,
5162
+ ...textMode ? { textRules: [] } : {}
4363
5163
  };
4364
5164
  registry.sheets.push(sheetInfo);
4365
5165
  return sheetInfo;
@@ -4427,7 +5227,7 @@ var SheetManager = class {
4427
5227
  if (rule.atRules && rule.atRules.length > 0) fullRule = rule.atRules.reduce((css, atRule) => `${atRule} { ${css} }`, baseRule);
4428
5228
  const styleElement = targetSheet.sheet;
4429
5229
  const styleSheet = this.getCSSSheet(targetSheet);
4430
- if (styleSheet && !this.config.forceTextInjection) {
5230
+ if (!targetSheet.textMode && styleSheet) {
4431
5231
  const maxIndex = styleSheet.cssRules.length;
4432
5232
  const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);
4433
5233
  const safeIndex = Math.min(Math.max(0, atomicRuleIndex), maxIndex);
@@ -4498,6 +5298,7 @@ var SheetManager = class {
4498
5298
  }
4499
5299
  } else if (styleElement) {
4500
5300
  const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);
5301
+ this.trackTextRule(targetSheet, atomicRuleIndex, fullRule);
4501
5302
  styleElement.textContent = (styleElement.textContent || "") + "\n" + fullRule;
4502
5303
  targetSheet.ruleCount++;
4503
5304
  insertedIndices.push(atomicRuleIndex);
@@ -4596,7 +5397,21 @@ var SheetManager = class {
4596
5397
  try {
4597
5398
  const texts = this.config.devMode && Array.isArray(ruleInfo.cssText) ? ruleInfo.cssText.slice() : [];
4598
5399
  const styleSheet = this.getCSSSheet(sheet);
4599
- if (styleSheet) {
5400
+ if (sheet.textMode) {
5401
+ let targetIndices;
5402
+ if (ruleInfo.indices && ruleInfo.indices.length > 0) targetIndices = ruleInfo.indices;
5403
+ else {
5404
+ const startIdx = Math.max(0, ruleInfo.ruleIndex);
5405
+ const endIdx = Math.min((sheet.textRules?.length ?? 0) - 1, Number.isFinite(ruleInfo.endRuleIndex) ? ruleInfo.endRuleIndex : startIdx);
5406
+ targetIndices = [];
5407
+ for (let idx = startIdx; idx <= endIdx; idx++) targetIndices.push(idx);
5408
+ }
5409
+ const deletedIndices = this.deleteTextRules(sheet, targetIndices);
5410
+ if (deletedIndices.length > 0) {
5411
+ sheet.ruleCount = Math.max(0, sheet.ruleCount - deletedIndices.length);
5412
+ this.adjustIndicesAfterDeletion(registry, ruleInfo.sheetIndex, Math.min(...deletedIndices), Math.max(...deletedIndices), deletedIndices.length, ruleInfo, deletedIndices);
5413
+ }
5414
+ } else if (styleSheet) {
4600
5415
  const rules = styleSheet.cssRules;
4601
5416
  if (ruleInfo.indices && ruleInfo.indices.length > 0) {
4602
5417
  const sortedIndices = [...ruleInfo.indices].sort((a, b) => b - a);
@@ -4721,8 +5536,8 @@ var SheetManager = class {
4721
5536
  const sheetInfo = registry.sheets[ruleInfo.sheetIndex];
4722
5537
  if (!sheetInfo || !sheetInfo.sheet && !sheetInfo.constructableSheet) continue;
4723
5538
  const styleSheet = this.getCSSSheet(sheetInfo);
4724
- if (!styleSheet) continue;
4725
- const maxRuleIndex = styleSheet.cssRules.length - 1;
5539
+ if (!sheetInfo.textMode && !styleSheet) continue;
5540
+ const maxRuleIndex = (sheetInfo.textMode ? sheetInfo.textRules?.length ?? 0 : styleSheet?.cssRules.length ?? 0) - 1;
4726
5541
  const startIdx = ruleInfo.ruleIndex;
4727
5542
  const endIdx = ruleInfo.endRuleIndex ?? ruleInfo.ruleIndex;
4728
5543
  if (startIdx < 0 || endIdx > maxRuleIndex || startIdx > endIdx) continue;
@@ -4755,7 +5570,7 @@ var SheetManager = class {
4755
5570
  /**
4756
5571
  * Get CSS text from all sheets (for SSR)
4757
5572
  */
4758
- getCssText(registry) {
5573
+ getCSSText(registry) {
4759
5574
  const cssChunks = [];
4760
5575
  for (const sheetInfo of registry.sheets) try {
4761
5576
  if (sheetInfo.constructableSheet) {
@@ -4876,10 +5691,13 @@ var SheetManager = class {
4876
5691
  const { css: cssSteps, declarations } = this.stepsToCSS(steps);
4877
5692
  const fullRule = `@keyframes ${name} { ${cssSteps} }`;
4878
5693
  const styleSheet = this.getCSSSheet(targetSheet);
4879
- if (styleSheet && !this.config.forceTextInjection) {
5694
+ if (!targetSheet.textMode && styleSheet) {
4880
5695
  const safeIndex = Math.min(Math.max(0, ruleIndex), styleSheet.cssRules.length);
4881
5696
  styleSheet.insertRule(fullRule, safeIndex);
4882
- } else if (targetSheet.sheet) targetSheet.sheet.textContent = (targetSheet.sheet.textContent || "") + "\n" + fullRule;
5697
+ } else if (targetSheet.sheet) {
5698
+ this.trackTextRule(targetSheet, ruleIndex, fullRule);
5699
+ targetSheet.sheet.textContent = (targetSheet.sheet.textContent || "") + "\n" + fullRule;
5700
+ }
4883
5701
  targetSheet.ruleCount++;
4884
5702
  return {
4885
5703
  info: {
@@ -4903,15 +5721,21 @@ var SheetManager = class {
4903
5721
  if (!sheet) return;
4904
5722
  try {
4905
5723
  const styleSheet = this.getCSSSheet(sheet);
4906
- if (styleSheet) {
5724
+ const adjustIndices = () => this.adjustIndicesAfterDeletion(registry, info.sheetIndex, info.ruleIndex, info.ruleIndex, 1, {
5725
+ className: "",
5726
+ ruleIndex: info.ruleIndex,
5727
+ sheetIndex: info.sheetIndex
5728
+ }, [info.ruleIndex]);
5729
+ if (sheet.textMode) {
5730
+ if (this.deleteTextRules(sheet, [info.ruleIndex]).length > 0) {
5731
+ sheet.ruleCount = Math.max(0, sheet.ruleCount - 1);
5732
+ adjustIndices();
5733
+ }
5734
+ } else if (styleSheet) {
4907
5735
  if (info.ruleIndex >= 0 && info.ruleIndex < styleSheet.cssRules.length) {
4908
5736
  styleSheet.deleteRule(info.ruleIndex);
4909
5737
  sheet.ruleCount = Math.max(0, sheet.ruleCount - 1);
4910
- this.adjustIndicesAfterDeletion(registry, info.sheetIndex, info.ruleIndex, info.ruleIndex, 1, {
4911
- className: "",
4912
- ruleIndex: info.ruleIndex,
4913
- sheetIndex: info.sheetIndex
4914
- }, [info.ruleIndex]);
5738
+ adjustIndices();
4915
5739
  }
4916
5740
  }
4917
5741
  } catch (error) {
@@ -5088,15 +5912,15 @@ var SheetManager = class {
5088
5912
  };
5089
5913
  //#endregion
5090
5914
  //#region src/font-face/index.ts
5091
- const FONT_FACE_KEY = "@fontFace";
5915
+ const FONT_FACE_KEY = "@font-face";
5092
5916
  /**
5093
- * Check if styles object has local @fontFace definition.
5917
+ * Check if styles object has local @font-face definition.
5094
5918
  */
5095
5919
  function hasLocalFontFace(styles) {
5096
5920
  return FONT_FACE_KEY in styles;
5097
5921
  }
5098
5922
  /**
5099
- * Extract local @fontFace from styles object.
5923
+ * Extract local @font-face from styles object.
5100
5924
  * Returns null if no local font faces (fast path).
5101
5925
  */
5102
5926
  function extractLocalFontFace(styles) {
@@ -5148,15 +5972,15 @@ function fontFaceContentHash(family, descriptors) {
5148
5972
  }
5149
5973
  //#endregion
5150
5974
  //#region src/counter-style/index.ts
5151
- const COUNTER_STYLE_KEY = "@counterStyle";
5975
+ const COUNTER_STYLE_KEY = "@counter-style";
5152
5976
  /**
5153
- * Check if styles object has local @counterStyle definition.
5977
+ * Check if styles object has local @counter-style definition.
5154
5978
  */
5155
5979
  function hasLocalCounterStyle(styles) {
5156
5980
  return COUNTER_STYLE_KEY in styles;
5157
5981
  }
5158
5982
  /**
5159
- * Extract local @counterStyle from styles object.
5983
+ * Extract local @counter-style from styles object.
5160
5984
  * Returns null if no local counter styles (fast path).
5161
5985
  */
5162
5986
  function extractLocalCounterStyle(styles) {
@@ -5504,15 +6328,15 @@ var StyleInjector = class {
5504
6328
  /**
5505
6329
  * Get CSS text from all sheets (for SSR)
5506
6330
  */
5507
- getCssText(options) {
6331
+ getCSSText(options) {
5508
6332
  const root = options?.root || document;
5509
6333
  const registry = this.sheetManager.getRegistry(root);
5510
- return this.sheetManager.getCssText(registry);
6334
+ return this.sheetManager.getCSSText(registry);
5511
6335
  }
5512
6336
  /**
5513
6337
  * Get CSS only for the provided tasty classNames (e.g., ["t0","t3"])
5514
6338
  */
5515
- getCssTextForClasses(classNames, options) {
6339
+ getCSSTextForClasses(classNames, options) {
5516
6340
  const root = options?.root || document;
5517
6341
  const registry = this.sheetManager.getRegistry(root);
5518
6342
  const cssChunks = [];
@@ -5653,18 +6477,51 @@ var StyleInjector = class {
5653
6477
  /**
5654
6478
  * Inject a CSS @counter-style rule.
5655
6479
  *
5656
- * Permanent and global — no dispose or ref-counting.
5657
- * Deduplicates by name (first definition wins).
6480
+ * Permanent and global — no dispose or ref-counting. Deduplicates by name.
6481
+ * By default a definition overrides a previously injected one of the same
6482
+ * name. Pass `weak: true` for global `configure()` definitions, which must
6483
+ * never clobber an existing rule (so component-local definitions win
6484
+ * regardless of injection order).
5658
6485
  */
5659
6486
  counterStyle(name, descriptors, options) {
5660
6487
  const root = options?.root || document;
5661
6488
  const registry = this.sheetManager.getRegistry(root);
5662
- if (registry.injectedCounterStyles.has(name)) return;
6489
+ const isWeak = options?.weak === true;
6490
+ const existingIsStrong = registry.injectedCounterStyles.get(name);
6491
+ if (existingIsStrong !== void 0) {
6492
+ if (isWeak || existingIsStrong === true) return;
6493
+ this.sheetManager.deleteGlobalRule(registry, `counterstyle:${name}`);
6494
+ }
5663
6495
  const rule = {
5664
6496
  selector: `@counter-style ${name}`,
5665
6497
  declarations: formatCounterStyleDeclarations(descriptors)
5666
6498
  };
5667
- if (this.sheetManager.insertGlobalRule(registry, [rule], `counterstyle:${name}`, root)) registry.injectedCounterStyles.add(name);
6499
+ if (this.sheetManager.insertGlobalRule(registry, [rule], `counterstyle:${name}`, root)) registry.injectedCounterStyles.set(name, !isWeak);
6500
+ }
6501
+ /**
6502
+ * Inject a CSS @function rule (custom function).
6503
+ *
6504
+ * Permanent and global — no dispose or ref-counting. Deduplicates by function
6505
+ * name. By default a definition overrides a previously injected one of the
6506
+ * same name. Pass `weak: true` for global `configure()` definitions, which
6507
+ * must never clobber an existing rule (so component-local definitions win
6508
+ * regardless of injection order).
6509
+ */
6510
+ func(name, definition, options) {
6511
+ const root = options?.root || document;
6512
+ const registry = this.sheetManager.getRegistry(root);
6513
+ const isWeak = options?.weak === true;
6514
+ const cssName = parseFunctionName(name);
6515
+ const existingIsStrong = registry.injectedFunctions.get(cssName);
6516
+ if (existingIsStrong !== void 0) {
6517
+ if (isWeak || existingIsStrong === true) return;
6518
+ this.sheetManager.deleteGlobalRule(registry, `function:${cssName}`);
6519
+ }
6520
+ const rule = {
6521
+ selector: formatFunctionPrelude(name, definition.args, definition.returns),
6522
+ declarations: formatFunctionDeclarations(definition)
6523
+ };
6524
+ if (this.sheetManager.insertGlobalRule(registry, [rule], `function:${cssName}`, root)) registry.injectedFunctions.set(cssName, !isWeak);
5668
6525
  }
5669
6526
  /**
5670
6527
  * Inject keyframes and return object with toString() and dispose()
@@ -5871,9 +6728,10 @@ var StyleInjector = class {
5871
6728
  const BUILTIN_STATES = new Set([
5872
6729
  "@starting",
5873
6730
  "@keyframes",
5874
- "@properties",
5875
- "@fontFace",
5876
- "@counterStyle",
6731
+ "@property",
6732
+ "@font-face",
6733
+ "@counter-style",
6734
+ "@function",
5877
6735
  "@supports",
5878
6736
  "@inherit"
5879
6737
  ]);
@@ -9583,6 +10441,8 @@ function parseStateKey(stateKey, options = {}) {
9583
10441
  * identical rules and emits all `@starting-style` rules last so they win
9584
10442
  * the cascade over their equal-specificity normal counterparts.
9585
10443
  */
10444
+ /** Any uppercase letter — a CSS property name should never contain one. */
10445
+ const RE_UPPER_CASE = /[A-Z]/;
9586
10446
  const pipelineCache = new Lru(5e3);
9587
10447
  /**
9588
10448
  * Check if a cache key exists in the pipeline cache.
@@ -9723,7 +10583,10 @@ function invokeHandler(handler, stateSnapshots, selectorSuffix) {
9723
10583
  if (!r || typeof r !== "object") continue;
9724
10584
  const { $, ...styleProps } = r;
9725
10585
  const declarations = {};
9726
- for (const [prop, val] of Object.entries(styleProps)) if (val != null && val !== "") declarations[prop] = String(val);
10586
+ for (const [prop, val] of Object.entries(styleProps)) {
10587
+ if (RE_UPPER_CASE.test(prop) && !prop.startsWith("--") && isDevEnv()) emitWarning("HANDLER_CAMEL_CASE_KEY", `A style handler returned "${prop}". CSS property names must be kebab-case ("${toSnakeCase(prop)}"). The declaration was emitted verbatim and the browser will ignore it.`);
10588
+ if (val != null && val !== "") declarations[prop] = String(val);
10589
+ }
9727
10590
  if (Object.keys(declarations).length === 0) continue;
9728
10591
  const suffixes = $ ? (Array.isArray($) ? $ : [$]).map((s) => selectorSuffix + normalizeSelectorSuffix(String(s))) : [selectorSuffix];
9729
10592
  for (const suffix of suffixes) computedRules.push({
@@ -10332,6 +11195,174 @@ function renderStyles(styles, classNameOrSelector, options, pipelineCacheKey) {
10332
11195
  })) };
10333
11196
  }
10334
11197
  //#endregion
11198
+ //#region src/styles/list.ts
11199
+ const BASE_STYLES = [
11200
+ "display",
11201
+ "font",
11202
+ "preset",
11203
+ "hide",
11204
+ "whiteSpace",
11205
+ "opacity",
11206
+ "transition"
11207
+ ];
11208
+ const POSITION_STYLES = [
11209
+ "gridArea",
11210
+ "order",
11211
+ "gridColumn",
11212
+ "gridRow",
11213
+ "placeSelf",
11214
+ "alignSelf",
11215
+ "justifySelf",
11216
+ "zIndex",
11217
+ "margin",
11218
+ "inset",
11219
+ "position",
11220
+ "scrollMargin"
11221
+ ];
11222
+ const BLOCK_INNER_STYLES = [
11223
+ "padding",
11224
+ "paddingInline",
11225
+ "paddingBlock",
11226
+ "overflow",
11227
+ "scrollbar",
11228
+ "textAlign"
11229
+ ];
11230
+ const BLOCK_OUTER_STYLES = [
11231
+ "border",
11232
+ "radius",
11233
+ "shadow",
11234
+ "outline"
11235
+ ];
11236
+ const BLOCK_STYLES = [...BLOCK_INNER_STYLES, ...BLOCK_OUTER_STYLES];
11237
+ const COLOR_STYLES = [
11238
+ "color",
11239
+ "fill",
11240
+ "fade",
11241
+ "image"
11242
+ ];
11243
+ const TEXT_STYLES = [
11244
+ "textTransform",
11245
+ "fontWeight",
11246
+ "fontStyle"
11247
+ ];
11248
+ const DIMENSION_STYLES = [
11249
+ "width",
11250
+ "height",
11251
+ "flexBasis",
11252
+ "flexGrow",
11253
+ "flexShrink",
11254
+ "flex"
11255
+ ];
11256
+ const FLOW_STYLES = [
11257
+ "flow",
11258
+ "place",
11259
+ "placeItems",
11260
+ "placeContent",
11261
+ "alignItems",
11262
+ "alignContent",
11263
+ "justifyItems",
11264
+ "justifyContent",
11265
+ "align",
11266
+ "justify",
11267
+ "gap",
11268
+ "columnGap",
11269
+ "rowGap",
11270
+ "gridColumns",
11271
+ "gridRows",
11272
+ "gridTemplate",
11273
+ "gridAreas"
11274
+ ];
11275
+ const CONTAINER_STYLES = [
11276
+ ...BASE_STYLES,
11277
+ ...COLOR_STYLES,
11278
+ ...DIMENSION_STYLES,
11279
+ ...POSITION_STYLES,
11280
+ ...BLOCK_STYLES,
11281
+ ...FLOW_STYLES
11282
+ ];
11283
+ const OUTER_STYLES = [
11284
+ ...POSITION_STYLES,
11285
+ ...DIMENSION_STYLES,
11286
+ ...BLOCK_OUTER_STYLES
11287
+ ];
11288
+ const INNER_STYLES = [
11289
+ ...BASE_STYLES,
11290
+ ...COLOR_STYLES,
11291
+ ...BLOCK_INNER_STYLES,
11292
+ ...FLOW_STYLES
11293
+ ];
11294
+ //#endregion
11295
+ //#region src/styles/base-props.ts
11296
+ /**
11297
+ * Registry for style properties promoted to base props on every tasty component.
11298
+ *
11299
+ * `BASE_STYLES` stays a frozen `as const` — a dozen type helpers in `src/types.ts`
11300
+ * are derived from it — so `configure({ baseStyleProps })` records the additions
11301
+ * here instead. A version counter lets each `tasty()` factory memoize its prop
11302
+ * list and refresh only when the registry changes: factories are created at module
11303
+ * eval, which can run *before* `configure()`, and `resetConfig()` (plus the
11304
+ * zero-runtime Babel worker) reopens configuration, so a one-shot lazy init would
11305
+ * go stale.
11306
+ */
11307
+ /** Props consumed by `tasty()` itself, which a style name must never shadow. */
11308
+ const RESERVED_PROP_NAMES = new Set([
11309
+ "as",
11310
+ "styles",
11311
+ "variant",
11312
+ "mods",
11313
+ "element",
11314
+ "qa",
11315
+ "qaVal",
11316
+ "className",
11317
+ "tokens",
11318
+ "style",
11319
+ "theme",
11320
+ "children",
11321
+ "ref",
11322
+ "key"
11323
+ ]);
11324
+ const VALID_NAME = /^[a-z][a-zA-Z0-9]*$/;
11325
+ const GTKEY = "__tasty_base_style_props__";
11326
+ const globalStore = globalThis;
11327
+ const baseStylePropsRegistry = globalStore[GTKEY] ?? (globalStore[GTKEY] = {
11328
+ list: [],
11329
+ version: 0
11330
+ });
11331
+ /**
11332
+ * Expose style properties as top-level props on every tasty component, in
11333
+ * addition to `BASE_STYLES`.
11334
+ *
11335
+ * Each name costs one property check per render of every component, forever, so
11336
+ * keep the list short. The effect is app-global and cannot be scoped to a subtree;
11337
+ * use a factory's own `styleProps` for anything narrower.
11338
+ */
11339
+ function registerBaseStyleProps(names) {
11340
+ const added = [];
11341
+ for (const name of names) {
11342
+ if (isDevEnv()) {
11343
+ if (!VALID_NAME.test(name)) {
11344
+ console.warn(`[Tasty] baseStyleProps entry "${name}" is invalid. Names must start with a lowercase letter and contain only letters and digits (capitalised names are sub-element selectors, and "@", "$", "#", ".", "&", "_" prefixes are reserved).`);
11345
+ continue;
11346
+ }
11347
+ if (RESERVED_PROP_NAMES.has(name)) {
11348
+ console.warn(`[Tasty] baseStyleProps entry "${name}" collides with a prop tasty() consumes itself and was ignored.`);
11349
+ continue;
11350
+ }
11351
+ } else if (!VALID_NAME.test(name) || RESERVED_PROP_NAMES.has(name)) continue;
11352
+ if (BASE_STYLES.includes(name) || baseStylePropsRegistry.list.includes(name) || added.includes(name)) continue;
11353
+ added.push(name);
11354
+ }
11355
+ if (added.length === 0) return;
11356
+ baseStylePropsRegistry.list = baseStylePropsRegistry.list.concat(added);
11357
+ baseStylePropsRegistry.version++;
11358
+ }
11359
+ /** Drop promoted base style props. Called by `resetConfig()`. */
11360
+ function resetBaseStyleProps() {
11361
+ if (baseStylePropsRegistry.list.length === 0) return;
11362
+ baseStylePropsRegistry.list = [];
11363
+ baseStylePropsRegistry.version++;
11364
+ }
11365
+ //#endregion
10335
11366
  //#region src/utils/typography.ts
10336
11367
  const RESERVED_PRESET_NAMES = new Set([
10337
11368
  "strong",
@@ -10409,6 +11440,8 @@ let currentConfig = null;
10409
11440
  let globalKeyframes = null;
10410
11441
  let globalFontFace = null;
10411
11442
  let globalCounterStyle = null;
11443
+ let globalFunction = null;
11444
+ let globalPolyfills = null;
10412
11445
  let globalProperties = null;
10413
11446
  let globalRecipes = null;
10414
11447
  let globalConfigTokens = null;
@@ -10416,6 +11449,8 @@ let globalStyles = null;
10416
11449
  const GTKEY_TOKENS = "__tasty_cfg_tokens__";
10417
11450
  const GTKEY_FONT_FACE = "__tasty_cfg_font_face__";
10418
11451
  const GTKEY_COUNTER_STYLE = "__tasty_cfg_counter_style__";
11452
+ const GTKEY_FUNCTION = "__tasty_cfg_function__";
11453
+ const GTKEY_POLYFILLS = "__tasty_cfg_polyfills__";
10419
11454
  const GTKEY_PROPERTIES = "__tasty_cfg_properties__";
10420
11455
  const GTKEY_GLOBAL_STYLES = "__tasty_cfg_global_styles__";
10421
11456
  function setOnGlobalThis(key, value) {
@@ -10429,6 +11464,8 @@ function clearGlobalThisConfig() {
10429
11464
  delete g[GTKEY_TOKENS];
10430
11465
  delete g[GTKEY_FONT_FACE];
10431
11466
  delete g[GTKEY_COUNTER_STYLE];
11467
+ delete g[GTKEY_FUNCTION];
11468
+ delete g[GTKEY_POLYFILLS];
10432
11469
  delete g[GTKEY_PROPERTIES];
10433
11470
  delete g[GTKEY_GLOBAL_STYLES];
10434
11471
  }
@@ -10559,7 +11596,8 @@ function markStylesGenerated() {
10559
11596
  const descriptors = Array.isArray(input) ? input : [input];
10560
11597
  for (const desc of descriptors) injector.fontFace(family, desc);
10561
11598
  }
10562
- if (globalCounterStyle && Object.keys(globalCounterStyle).length > 0) for (const [name, descriptors] of Object.entries(globalCounterStyle)) injector.counterStyle(name, descriptors);
11599
+ if (globalCounterStyle && Object.keys(globalCounterStyle).length > 0) for (const [name, descriptors] of Object.entries(globalCounterStyle)) injector.counterStyle(name, descriptors, { weak: true });
11600
+ if (globalFunction && Object.keys(globalFunction).length > 0) for (const [name, definition] of Object.entries(globalFunction)) injector.func(name, definition, { weak: true });
10563
11601
  if (globalConfigTokens && Object.keys(globalConfigTokens).length > 0) {
10564
11602
  const tokenRules = renderStyles(globalConfigTokens, ":root");
10565
11603
  if (tokenRules.length > 0) injector.injectGlobal(tokenRules);
@@ -10601,8 +11639,11 @@ function setGlobalKeyframes(keyframes) {
10601
11639
  warnOnce("keyframes-after-styles", "[Tasty] Cannot update keyframes after styles have been generated.\nThe new keyframes will be ignored.");
10602
11640
  return;
10603
11641
  }
10604
- globalKeyframes = keyframes;
10605
- _hasGlobalKeyframes = Object.keys(keyframes).length > 0;
11642
+ globalKeyframes = {
11643
+ ...globalKeyframes ?? {},
11644
+ ...keyframes
11645
+ };
11646
+ _hasGlobalKeyframes = Object.keys(globalKeyframes).length > 0;
10606
11647
  }
10607
11648
  /**
10608
11649
  * Set global properties (called from configure).
@@ -10613,7 +11654,10 @@ function setGlobalProperties(properties) {
10613
11654
  warnOnce("properties-after-styles", "[Tasty] Cannot update properties after styles have been generated.\nThe new properties will be ignored.");
10614
11655
  return;
10615
11656
  }
10616
- globalProperties = properties;
11657
+ globalProperties = {
11658
+ ...globalProperties ?? getFromGlobalThis(GTKEY_PROPERTIES) ?? {},
11659
+ ...properties
11660
+ };
10617
11661
  setOnGlobalThis(GTKEY_PROPERTIES, globalProperties);
10618
11662
  }
10619
11663
  /**
@@ -10634,7 +11678,7 @@ function getEffectiveProperties() {
10634
11678
  * Returns null if no font faces configured.
10635
11679
  * Reads from globalThis first for cross-module SSR support.
10636
11680
  */
10637
- function getGlobalFontFace() {
11681
+ function getGlobalFontFaces() {
10638
11682
  return globalFontFace ?? getFromGlobalThis(GTKEY_FONT_FACE) ?? null;
10639
11683
  }
10640
11684
  /**
@@ -10643,10 +11687,13 @@ function getGlobalFontFace() {
10643
11687
  */
10644
11688
  function setGlobalFontFace(fontFace) {
10645
11689
  if (stylesGenerated) {
10646
- warnOnce("fontface-after-styles", "[Tasty] Cannot update fontFace after styles have been generated.\nThe new font faces will be ignored.");
11690
+ warnOnce("fontface-after-styles", "[Tasty] Cannot update fontFaces after styles have been generated.\nThe new font faces will be ignored.");
10647
11691
  return;
10648
11692
  }
10649
- globalFontFace = fontFace;
11693
+ globalFontFace = {
11694
+ ...getGlobalFontFaces() ?? {},
11695
+ ...fontFace
11696
+ };
10650
11697
  setOnGlobalThis(GTKEY_FONT_FACE, globalFontFace);
10651
11698
  }
10652
11699
  /**
@@ -10654,7 +11701,7 @@ function setGlobalFontFace(fontFace) {
10654
11701
  * Returns null if no counter styles configured.
10655
11702
  * Reads from globalThis first for cross-module SSR support.
10656
11703
  */
10657
- function getGlobalCounterStyle() {
11704
+ function getGlobalCounterStyles() {
10658
11705
  return globalCounterStyle ?? getFromGlobalThis(GTKEY_COUNTER_STYLE) ?? null;
10659
11706
  }
10660
11707
  /**
@@ -10663,13 +11710,61 @@ function getGlobalCounterStyle() {
10663
11710
  */
10664
11711
  function setGlobalCounterStyle(counterStyle) {
10665
11712
  if (stylesGenerated) {
10666
- warnOnce("counterstyle-after-styles", "[Tasty] Cannot update counterStyle after styles have been generated.\nThe new counter styles will be ignored.");
11713
+ warnOnce("counterstyle-after-styles", "[Tasty] Cannot update counterStyles after styles have been generated.\nThe new counter styles will be ignored.");
10667
11714
  return;
10668
11715
  }
10669
- globalCounterStyle = counterStyle;
11716
+ globalCounterStyle = {
11717
+ ...getGlobalCounterStyles() ?? {},
11718
+ ...counterStyle
11719
+ };
10670
11720
  setOnGlobalThis(GTKEY_COUNTER_STYLE, globalCounterStyle);
10671
11721
  }
10672
11722
  /**
11723
+ * Get global @function configuration.
11724
+ * Returns null if no functions configured.
11725
+ * Reads from globalThis first for cross-module SSR support.
11726
+ */
11727
+ function getGlobalFunctions() {
11728
+ return globalFunction ?? getFromGlobalThis(GTKEY_FUNCTION) ?? null;
11729
+ }
11730
+ /**
11731
+ * Set global functions (called from configure).
11732
+ * Internal use only.
11733
+ */
11734
+ function setGlobalFunction(functions) {
11735
+ if (stylesGenerated) {
11736
+ warnOnce("function-after-styles", "[Tasty] Cannot update functions after styles have been generated.\nThe new functions will be ignored.");
11737
+ return;
11738
+ }
11739
+ globalFunction = {
11740
+ ...getGlobalFunctions() ?? {},
11741
+ ...functions
11742
+ };
11743
+ setOnGlobalThis(GTKEY_FUNCTION, globalFunction);
11744
+ }
11745
+ /**
11746
+ * Whether the CSS `@function` polyfill (inline expansion) is enabled.
11747
+ * Reads from globalThis first for cross-module SSR/zero-runtime support.
11748
+ */
11749
+ function isFunctionsPolyfillEnabled() {
11750
+ return (globalPolyfills ?? getFromGlobalThis(GTKEY_POLYFILLS) ?? null)?.functions === true;
11751
+ }
11752
+ /**
11753
+ * Set global polyfill toggles (called from configure).
11754
+ * Internal use only.
11755
+ */
11756
+ function setGlobalPolyfills(polyfills) {
11757
+ if (stylesGenerated) {
11758
+ warnOnce("polyfills-after-styles", "[Tasty] Cannot update polyfills after styles have been generated.\nThe new polyfill toggles will be ignored.");
11759
+ return;
11760
+ }
11761
+ globalPolyfills = {
11762
+ ...globalPolyfills ?? {},
11763
+ ...polyfills
11764
+ };
11765
+ setOnGlobalThis(GTKEY_POLYFILLS, globalPolyfills);
11766
+ }
11767
+ /**
10673
11768
  * Check if any global recipes are configured.
10674
11769
  * Fast path: returns false if no recipes were ever set.
10675
11770
  */
@@ -10699,7 +11794,10 @@ function setGlobalRecipes(recipes) {
10699
11794
  if (key === "recipe") warnOnce(`recipe-recursive-${name}`, `[Tasty] Recipe "${name}" contains a "recipe" key. Recipes cannot reference other recipes. Use space-separated names for composition: recipe: 'base elevated'.`);
10700
11795
  }
10701
11796
  }
10702
- globalRecipes = recipes;
11797
+ globalRecipes = {
11798
+ ...globalRecipes ?? {},
11799
+ ...recipes
11800
+ };
10703
11801
  }
10704
11802
  /**
10705
11803
  * Get global token styles for :root injection.
@@ -10783,8 +11881,18 @@ function configure(config = {}) {
10783
11881
  if (config.namePrefix !== void 0) validateNamePrefix(config.namePrefix);
10784
11882
  let mergedStates = {};
10785
11883
  let mergedUnits = {};
10786
- let mergedFuncs = {};
11884
+ let mergedFunctions = {};
10787
11885
  let mergedHandlers = {};
11886
+ /** Where each handler key came from, so registration warnings can name it. */
11887
+ const handlerSources = /* @__PURE__ */ new Map();
11888
+ let mergedPropHandlers = {};
11889
+ /** Where each prop handler key came from, for its warnings. */
11890
+ const propHandlerSources = /* @__PURE__ */ new Map();
11891
+ const mergedBaseStyleProps = [];
11892
+ let mergedProperties = {};
11893
+ let mergedKeyframes = {};
11894
+ let mergedFontFaces = {};
11895
+ let mergedCounterStyles = {};
10788
11896
  let mergedReplaceTokens = {};
10789
11897
  let mergedConfigTokens = {};
10790
11898
  let mergedRecipes = {};
@@ -10799,13 +11907,40 @@ function configure(config = {}) {
10799
11907
  ...mergedUnits,
10800
11908
  ...plugin.units
10801
11909
  };
10802
- if (plugin.funcs) mergedFuncs = {
10803
- ...mergedFuncs,
10804
- ...plugin.funcs
11910
+ if (plugin.functions) mergedFunctions = {
11911
+ ...mergedFunctions,
11912
+ ...plugin.functions
10805
11913
  };
10806
- if (plugin.handlers) mergedHandlers = {
10807
- ...mergedHandlers,
10808
- ...plugin.handlers
11914
+ if (plugin.handlers) {
11915
+ mergedHandlers = {
11916
+ ...mergedHandlers,
11917
+ ...plugin.handlers
11918
+ };
11919
+ for (const key of Object.keys(plugin.handlers)) handlerSources.set(key, `plugin "${plugin.name}"`);
11920
+ }
11921
+ if (plugin.propHandlers) {
11922
+ mergedPropHandlers = {
11923
+ ...mergedPropHandlers,
11924
+ ...plugin.propHandlers
11925
+ };
11926
+ for (const key of Object.keys(plugin.propHandlers)) propHandlerSources.set(key, `plugin "${plugin.name}"`);
11927
+ }
11928
+ if (plugin.baseStyleProps) mergedBaseStyleProps.push(...plugin.baseStyleProps);
11929
+ if (plugin.properties) mergedProperties = {
11930
+ ...mergedProperties,
11931
+ ...plugin.properties
11932
+ };
11933
+ if (plugin.keyframes) mergedKeyframes = {
11934
+ ...mergedKeyframes,
11935
+ ...plugin.keyframes
11936
+ };
11937
+ if (plugin.fontFaces) mergedFontFaces = {
11938
+ ...mergedFontFaces,
11939
+ ...plugin.fontFaces
11940
+ };
11941
+ if (plugin.counterStyles) mergedCounterStyles = {
11942
+ ...mergedCounterStyles,
11943
+ ...plugin.counterStyles
10809
11944
  };
10810
11945
  if (plugin.replaceTokens) mergedReplaceTokens = {
10811
11946
  ...mergedReplaceTokens,
@@ -10836,13 +11971,40 @@ function configure(config = {}) {
10836
11971
  ...mergedUnits,
10837
11972
  ...config.units
10838
11973
  };
10839
- if (config.funcs) mergedFuncs = {
10840
- ...mergedFuncs,
10841
- ...config.funcs
11974
+ if (config.functions) mergedFunctions = {
11975
+ ...mergedFunctions,
11976
+ ...config.functions
11977
+ };
11978
+ if (config.handlers) {
11979
+ mergedHandlers = {
11980
+ ...mergedHandlers,
11981
+ ...config.handlers
11982
+ };
11983
+ for (const key of Object.keys(config.handlers)) handlerSources.set(key, "configure()");
11984
+ }
11985
+ if (config.propHandlers) {
11986
+ mergedPropHandlers = {
11987
+ ...mergedPropHandlers,
11988
+ ...config.propHandlers
11989
+ };
11990
+ for (const key of Object.keys(config.propHandlers)) propHandlerSources.set(key, "configure()");
11991
+ }
11992
+ if (config.baseStyleProps) mergedBaseStyleProps.push(...config.baseStyleProps);
11993
+ if (config.properties) mergedProperties = {
11994
+ ...mergedProperties,
11995
+ ...config.properties
11996
+ };
11997
+ if (config.keyframes) mergedKeyframes = {
11998
+ ...mergedKeyframes,
11999
+ ...config.keyframes
12000
+ };
12001
+ if (config.fontFaces) mergedFontFaces = {
12002
+ ...mergedFontFaces,
12003
+ ...config.fontFaces
10842
12004
  };
10843
- if (config.handlers) mergedHandlers = {
10844
- ...mergedHandlers,
10845
- ...config.handlers
12005
+ if (config.counterStyles) mergedCounterStyles = {
12006
+ ...mergedCounterStyles,
12007
+ ...config.counterStyles
10846
12008
  };
10847
12009
  if (config.replaceTokens) mergedReplaceTokens = {
10848
12010
  ...mergedReplaceTokens,
@@ -10886,20 +12048,31 @@ function configure(config = {}) {
10886
12048
  ...mergedUnits
10887
12049
  });
10888
12050
  }
10889
- if (Object.keys(mergedFuncs).length > 0) {
10890
- const currentFuncs = getGlobalFuncs();
12051
+ if (config.polyfills) setGlobalPolyfills(config.polyfills);
12052
+ const { parseFuncs, functionDefs } = splitFunctions(mergedFunctions, (key, kind) => {
12053
+ warnOnce(`functions-mismatch-${key}`, kind === "expected-definition" ? `[Tasty] functions["${key}"]: a "$$"-prefixed key denotes a CSS @function and expects a definition object, but received a function. Entry ignored.` : `[Tasty] functions["${key}"]: a bare key denotes a parse function and expects a function value, but received an object. Did you mean "$$${key}"? Entry ignored.`);
12054
+ });
12055
+ if (Object.keys(parseFuncs).length > 0) {
12056
+ const currentFuncs = getGlobalParseFunctions();
10891
12057
  const finalFuncs = {
10892
12058
  ...currentFuncs,
10893
- ...mergedFuncs
12059
+ ...parseFuncs
10894
12060
  };
10895
- parser.setFuncs(finalFuncs);
10896
- Object.assign(currentFuncs, mergedFuncs);
10897
- }
10898
- if (config.keyframes) setGlobalKeyframes(config.keyframes);
10899
- if (config.properties) setGlobalProperties(config.properties);
10900
- if (config.fontFace) setGlobalFontFace(config.fontFace);
10901
- if (config.counterStyle) setGlobalCounterStyle(config.counterStyle);
10902
- if (Object.keys(mergedHandlers).length > 0) for (const [name, definition] of Object.entries(mergedHandlers)) registerHandler(normalizeHandlerDefinition(name, definition));
12061
+ parser.setFunctions(finalFuncs);
12062
+ Object.assign(currentFuncs, parseFuncs);
12063
+ }
12064
+ if (Object.keys(functionDefs).length > 0) if (isFunctionsPolyfillEnabled()) for (const [name, definition] of Object.entries(functionDefs)) registerFunctionPolyfill(name, definition);
12065
+ else setGlobalFunction(functionDefs);
12066
+ if (Object.keys(mergedKeyframes).length > 0) setGlobalKeyframes(mergedKeyframes);
12067
+ if (Object.keys(mergedProperties).length > 0) setGlobalProperties(mergedProperties);
12068
+ if (Object.keys(mergedFontFaces).length > 0) setGlobalFontFace(mergedFontFaces);
12069
+ if (Object.keys(mergedCounterStyles).length > 0) setGlobalCounterStyle(mergedCounterStyles);
12070
+ if (Object.keys(mergedHandlers).length > 0) for (const [name, definition] of Object.entries(mergedHandlers)) registerHandler(normalizeHandlerDefinition(name, definition), {
12071
+ key: name,
12072
+ source: handlerSources.get(name)
12073
+ });
12074
+ if (Object.keys(mergedPropHandlers).length > 0) for (const [name, definition] of Object.entries(mergedPropHandlers)) registerPropHandler(name, definition, { source: propHandlerSources.get(name) });
12075
+ if (mergedBaseStyleProps.length > 0) registerBaseStyleProps(mergedBaseStyleProps);
10903
12076
  if (Object.keys(mergedReplaceTokens).length > 0) {
10904
12077
  const processedTokens = {};
10905
12078
  for (const [key, value] of Object.entries(mergedReplaceTokens)) if (key.startsWith("#")) {
@@ -10913,7 +12086,7 @@ function configure(config = {}) {
10913
12086
  if (Object.keys(mergedConfigTokens).length > 0) setGlobalConfigTokens(mergedConfigTokens);
10914
12087
  if (Object.keys(mergedRecipes).length > 0) setGlobalRecipes(mergedRecipes);
10915
12088
  if (Object.keys(mergedGlobalStyles).length > 0) setGlobalStyles(mergedGlobalStyles);
10916
- const { states: _states, parserCacheSize: _parserCacheSize, units: _units, funcs: _funcs, plugins: _plugins, keyframes: _keyframes, properties: _properties, fontFace: _fontFace, counterStyle: _counterStyle, handlers: _handlers, tokens: _tokens, replaceTokens: _replaceTokens, recipes: _recipes, colorSpace: _colorSpace, presets: _presets, globalStyles: _globalStyles, ...injectorConfig } = config;
12089
+ const { states: _states, parserCacheSize: _parserCacheSize, units: _units, functions: _functions, polyfills: _polyfills, plugins: _plugins, keyframes: _keyframes, properties: _properties, fontFaces: _fontFaces, counterStyles: _counterStyles, handlers: _handlers, propHandlers: _propHandlers, baseStyleProps: _baseStyleProps, tokens: _tokens, replaceTokens: _replaceTokens, recipes: _recipes, colorSpace: _colorSpace, presets: _presets, globalStyles: _globalStyles, ...injectorConfig } = config;
10917
12090
  const fullConfig = {
10918
12091
  ...createDefaultConfig(),
10919
12092
  ...currentConfig,
@@ -10963,19 +12136,27 @@ function resetConfig() {
10963
12136
  globalProperties = null;
10964
12137
  globalFontFace = null;
10965
12138
  globalCounterStyle = null;
12139
+ globalFunction = null;
12140
+ globalPolyfills = null;
10966
12141
  globalRecipes = null;
10967
12142
  globalConfigTokens = null;
10968
12143
  globalStyles = null;
10969
12144
  clearGlobalThisConfig();
10970
12145
  resetGlobalPredefinedTokens();
12146
+ resetGlobalParseFunctions();
12147
+ resetFunctionPolyfills();
10971
12148
  resetHandlers();
12149
+ resetStyleChunks();
12150
+ resetPropHandlers();
12151
+ resetBaseStyleProps();
10972
12152
  resetColorSpace();
10973
12153
  clearPipelineCache();
10974
12154
  emittedWarnings.clear();
12155
+ resetStyleWarnings();
10975
12156
  const storage = typeof window !== "undefined" ? window : globalThis;
10976
12157
  delete storage[GLOBAL_INJECTOR_KEY];
10977
12158
  }
10978
12159
  //#endregion
10979
- 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 };
12160
+ export { SheetManager as $, getNamedColorHex as $t, FLOW_STYLES as A, registerFunctionPolyfill as At, createStateParserContext as B, stringifyStyles as Bt, BASE_STYLES as C, LAYOUT_CHUNK_STYLES as Ct, COLOR_STYLES as D, formatFunctionRule as Dt, BLOCK_STYLES as E, extractLocalFunctions as Et, hasPipelineCacheEntry as F, getGlobalParser as Ft, StyleInjector as G, createColorFunc as Gt, extractPredefinedStateRefs as H, okhstPlugin as Ht, isSelector as I, getGlobalPredefinedTokens as It, hasLocalCounterStyle as J, colorInitialValueToComponents as Jt, extractLocalCounterStyle as K, isDevEnv as Kt, renderStyles as L, normalizeColorTokenValue as Lt, OUTER_STYLES as M, CUSTOM_UNITS as Mt, POSITION_STYLES as N, DIRECTIONS as Nt, CONTAINER_STYLES as O, hasLocalFunctions as Ot, TEXT_STYLES as P, filterMods as Pt, hasLocalFontFace as Q, getComponentPropertySyntax as Qt, parseStateKey as R, parseColor as Rt, baseStylePropsRegistry as S, FONT_CHUNK_STYLES as St, BLOCK_OUTER_STYLES as T, STYLE_TO_CHUNK as Tt, getGlobalPredefinedStates as U, okhslFunction as Ut, extractLocalPredefinedStates as V, okhstFunction as Vt, setGlobalPredefinedStates as W, okhslPlugin as Wt, fontFaceContentHash as X, getColorSpaceFunc as Xt, extractLocalFontFace as Y, getColorSpaceComponents as Yt, formatFontFaceRule as Z, getColorSpaceSuffix as Zt, isFunctionsPolyfillEnabled as _, propHandlerRegistry as _t, getGlobalCounterStyles as a, Lru as an, DEFAULT_NAME_PREFIX as at, resetConfig as b, DIMENSION_CHUNK_STYLES as bt, getGlobalInjector as c, makeCounterStyleName as ct, getGlobalStyles as d, validateNamePrefix as dt, getRgbValuesFromRgbaString as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, hashString as ft, isConfigLocked as g, parsePropertyToken as gt, hasStylesGenerated as h, hasLocalProperties as ht, getGlobalConfigTokens as i, resolveFunctionColor as in, PropertyTypeResolver as it, INNER_STYLES as j, registerLocalFunctionPolyfills as jt, DIMENSION_STYLES as k, parseFunctionName as kt, getGlobalKeyframes as l, makeKeyframeName as lt, hasGlobalRecipes as m, getEffectiveDefinition as mt, getConfig as n, hslToRgbValues as nn, styleHandlers as nt, getGlobalFontFaces as o, DEFAULT_ZERO_NAME_PREFIX as ot, hasGlobalKeyframes as p, extractLocalProperties as pt, formatCounterStyleRule as q, StyleParser as qt, getEffectiveProperties as r, strToRgb as rn, createStyle as rt, getGlobalFunctions as s, makeClassName as st, configure as t, hexToRgb as tn, defineHandler as tt, getGlobalRecipes as u, tastyClassRegex as ut, isTestEnvironment as v, APPEARANCE_CHUNK_STYLES as vt, BLOCK_INNER_STYLES as w, POSITION_CHUNK_STYLES as wt, generateTypographyTokens as x, DISPLAY_CHUNK_STYLES as xt, markStylesGenerated as y, CHUNK_NAMES as yt, camelToKebab as z, parseStyle as zt };
10980
12161
 
10981
- //# sourceMappingURL=config-YDAcLaVf.js.map
12162
+ //# sourceMappingURL=config-CCcE_tqx.js.map