@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.
- package/README.md +32 -7
- package/dist/astro-B53trJyX.js +156 -0
- package/dist/astro-B53trJyX.js.map +1 -0
- package/dist/babel-HlEpSZrj.d.ts +83 -0
- package/dist/{collector-DYA5AOwr.d.ts → collector-BPzZezAG.d.ts} +44 -7
- package/dist/{collector-AHZaBSv8.js → collector-D14DcZx9.js} +75 -14
- package/dist/collector-D14DcZx9.js.map +1 -0
- package/dist/{config-YDAcLaVf.js → config-CCcE_tqx.js} +2312 -1131
- package/dist/config-CCcE_tqx.js.map +1 -0
- package/dist/{config-BtK9fUaz.d.ts → config-D2INrjr_.d.ts} +252 -25
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +6 -6
- package/dist/{core-CS4bzqGu.js → core-B5d0IPi2.js} +85 -133
- package/dist/core-B5d0IPi2.js.map +1 -0
- package/dist/{css-writer-BXSANVrq.js → css-writer-B5DBoxhf.js} +54 -15
- package/dist/css-writer-B5DBoxhf.js.map +1 -0
- package/dist/{format-rules-CYriCDwq.js → format-rules-BbKDjsfO.js} +2 -2
- package/dist/{format-rules-CYriCDwq.js.map → format-rules-BbKDjsfO.js.map} +1 -1
- package/dist/{hydrate-GVTorHpU.js → hydrate-CwALW3J9.js} +3 -11
- package/dist/{hydrate-GVTorHpU.js.map → hydrate-CwALW3J9.js.map} +1 -1
- package/dist/{index-BYtnj_gA.d.ts → index-BAAbdfRs.d.ts} +228 -153
- package/dist/{index-B9ih23uv.d.ts → index-l_NdJSUS.d.ts} +172 -61
- package/dist/index.d.ts +5 -5
- package/dist/index.js +191 -56
- package/dist/index.js.map +1 -1
- package/dist/{keyframes-CtcSlw4k.js → keyframes-B8M1FmtE.js} +10 -181
- package/dist/keyframes-B8M1FmtE.js.map +1 -0
- package/dist/{merge-styles-D4ITH4bc.js → merge-styles--1zOSFlS.js} +2 -2
- package/dist/{merge-styles-D4ITH4bc.js.map → merge-styles--1zOSFlS.js.map} +1 -1
- package/dist/{merge-styles-BZV-XAwX.d.ts → merge-styles-NEvqayL_.d.ts} +2 -2
- package/dist/{resolve-recipes-llr0COs8.js → resolve-recipes-Caix5NnH.js} +3 -3
- package/dist/{resolve-recipes-llr0COs8.js.map → resolve-recipes-Caix5NnH.js.map} +1 -1
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-middleware.js +1 -1
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +1 -5
- package/dist/ssr/astro.js +2 -147
- package/dist/ssr/index.d.ts +2 -9
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +5 -6
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +2 -80
- package/dist/zero/babel.js +27 -23
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +16 -2
- package/dist/zero/index.js +2 -2
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +1 -1
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +3 -1
- package/docs/ai-agents.md +21 -7
- package/docs/configuration.md +220 -30
- package/docs/debug.md +7 -7
- package/docs/design-system.md +2 -2
- package/docs/dsl.md +68 -8
- package/docs/injector.md +46 -4
- package/docs/methodology.md +1 -1
- package/docs/migration-v3.md +285 -0
- package/docs/pipeline.md +3 -3
- package/docs/plugins.md +349 -0
- package/docs/react-api.md +82 -3
- package/docs/ssr.md +40 -33
- package/docs/styles.md +27 -9
- package/docs/tasty-static.md +8 -6
- package/package.json +7 -7
- package/tasty.config.ts +4 -0
- package/dist/collector-AHZaBSv8.js.map +0 -1
- package/dist/config-YDAcLaVf.js.map +0 -1
- package/dist/core-CS4bzqGu.js.map +0 -1
- package/dist/css-writer-BXSANVrq.js.map +0 -1
- package/dist/keyframes-CtcSlw4k.js.map +0 -1
- 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
|
-
|
|
1313
|
-
|
|
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/
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
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
|
|
1741
|
-
|
|
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: `--${
|
|
1377
|
+
processed: `--${rest}`
|
|
1744
1378
|
};
|
|
1745
1379
|
}
|
|
1746
1380
|
if (token.startsWith("##")) {
|
|
1747
|
-
const
|
|
1748
|
-
|
|
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: `--${
|
|
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(/^(
|
|
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.
|
|
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.
|
|
1902
|
-
const tmp = new StyleParser(opts).process(inner);
|
|
1903
|
-
return classify(opts.
|
|
1904
|
-
...opts,
|
|
1905
|
-
|
|
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
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
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
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
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
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
-
|
|
2023
|
-
continue;
|
|
2183
|
+
return {};
|
|
2024
2184
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2237
|
+
const result = "{" + parts.join(",") + "}";
|
|
2238
|
+
_topLevelCache.set(styles, result);
|
|
2239
|
+
return result;
|
|
2053
2240
|
}
|
|
2054
2241
|
//#endregion
|
|
2055
|
-
//#region src/
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
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
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
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
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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/
|
|
2165
|
-
|
|
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
|
-
*
|
|
2168
|
-
*
|
|
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
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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
|
-
*
|
|
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
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
-
*
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2193
|
-
*
|
|
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
|
|
2196
|
-
|
|
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
|
-
*
|
|
2700
|
+
* Drop custom chunk assignments and restore the built-in baseline.
|
|
2225
2701
|
*
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
2716
|
+
* Props middleware for tasty components.
|
|
2242
2717
|
*
|
|
2243
|
-
*
|
|
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
|
-
*
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
2254
|
-
*
|
|
2255
|
-
*
|
|
2256
|
-
*
|
|
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
|
|
2263
|
-
|
|
2264
|
-
|
|
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
|
-
|
|
2267
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2276
|
-
*
|
|
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
|
-
*
|
|
2283
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
2292
|
-
*
|
|
2293
|
-
|
|
2294
|
-
|
|
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
|
-
*
|
|
2297
|
-
*
|
|
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
|
-
*
|
|
2301
|
-
*
|
|
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
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
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
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
*
|
|
2318
|
-
*
|
|
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
|
|
2321
|
-
* @returns
|
|
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
|
|
2324
|
-
if (value
|
|
2325
|
-
|
|
2326
|
-
return
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
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
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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
|
|
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
|
-
*
|
|
2382
|
-
*
|
|
3054
|
+
* Fast string hash (djb2) for deduplication keys.
|
|
3055
|
+
* Not cryptographic — just collision-resistant enough for cache keys.
|
|
2383
3056
|
*/
|
|
2384
|
-
function
|
|
2385
|
-
|
|
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
|
-
*
|
|
2389
|
-
*
|
|
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
|
-
|
|
2392
|
-
|
|
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
|
-
*
|
|
2396
|
-
*
|
|
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
|
-
|
|
3094
|
+
const NAME_PREFIX_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$/;
|
|
2399
3095
|
/**
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
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
|
|
2407
|
-
|
|
2408
|
-
|
|
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
|
-
*
|
|
2425
|
-
*
|
|
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
|
|
2428
|
-
return
|
|
3110
|
+
function makeClassName(prefix, hash) {
|
|
3111
|
+
return `${prefix}${hash}`;
|
|
2429
3112
|
}
|
|
2430
3113
|
/**
|
|
2431
|
-
*
|
|
2432
|
-
*
|
|
2433
|
-
*
|
|
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
|
|
2436
|
-
|
|
2437
|
-
getOrCreateParser().clearCache();
|
|
3119
|
+
function makeKeyframeName(prefix, suffix) {
|
|
3120
|
+
return `${prefix}k${suffix}`;
|
|
2438
3121
|
}
|
|
2439
3122
|
/**
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
2442
|
-
*
|
|
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
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
return
|
|
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
|
-
*
|
|
2454
|
-
*
|
|
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
|
|
2457
|
-
|
|
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
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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
|
-
|
|
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
|
|
3201
|
-
span
|
|
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
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
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]] =
|
|
3217
|
-
dirs[DIRECTIONS[(d + 3) % 4]] =
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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 = "@
|
|
5915
|
+
const FONT_FACE_KEY = "@font-face";
|
|
5092
5916
|
/**
|
|
5093
|
-
* Check if styles object has local @
|
|
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 @
|
|
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 = "@
|
|
5975
|
+
const COUNTER_STYLE_KEY = "@counter-style";
|
|
5152
5976
|
/**
|
|
5153
|
-
* Check if styles object has local @
|
|
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 @
|
|
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
|
-
|
|
6331
|
+
getCSSText(options) {
|
|
5508
6332
|
const root = options?.root || document;
|
|
5509
6333
|
const registry = this.sheetManager.getRegistry(root);
|
|
5510
|
-
return this.sheetManager.
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
"@
|
|
5875
|
-
"@
|
|
5876
|
-
"@
|
|
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))
|
|
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 =
|
|
10605
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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.
|
|
10803
|
-
...
|
|
10804
|
-
...plugin.
|
|
11910
|
+
if (plugin.functions) mergedFunctions = {
|
|
11911
|
+
...mergedFunctions,
|
|
11912
|
+
...plugin.functions
|
|
10805
11913
|
};
|
|
10806
|
-
if (plugin.handlers)
|
|
10807
|
-
|
|
10808
|
-
|
|
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.
|
|
10840
|
-
...
|
|
10841
|
-
...config.
|
|
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.
|
|
10844
|
-
...
|
|
10845
|
-
...config.
|
|
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 (
|
|
10890
|
-
|
|
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
|
-
...
|
|
12059
|
+
...parseFuncs
|
|
10894
12060
|
};
|
|
10895
|
-
parser.
|
|
10896
|
-
Object.assign(currentFuncs,
|
|
10897
|
-
}
|
|
10898
|
-
if (
|
|
10899
|
-
|
|
10900
|
-
if (
|
|
10901
|
-
if (
|
|
10902
|
-
if (Object.keys(
|
|
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,
|
|
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 {
|
|
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-
|
|
12162
|
+
//# sourceMappingURL=config-CCcE_tqx.js.map
|