@tenphi/tasty 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -0
  2. package/dist/{babel-R2qT7yLN.d.ts → babel-D8dm92E_.d.ts} +2 -2
  3. package/dist/{collector-D8o4Wdq-.d.ts → collector-BFuqdF2F.d.ts} +2 -2
  4. package/dist/{collector-BpkjNQDo.js → collector-Di3C8btw.js} +3 -3
  5. package/dist/{collector-BpkjNQDo.js.map → collector-Di3C8btw.js.map} +1 -1
  6. package/dist/{config-DOCuTykY.js → config-BrDt11hO.js} +861 -290
  7. package/dist/config-BrDt11hO.js.map +1 -0
  8. package/dist/{config-De8L9NWM.d.ts → config-DH7I0Ggx.d.ts} +73 -2
  9. package/dist/core/index.d.ts +5 -5
  10. package/dist/core/index.js +6 -6
  11. package/dist/{core-p6iTbQnn.js → core-YWn1zgh4.js} +9 -10
  12. package/dist/core-YWn1zgh4.js.map +1 -0
  13. package/dist/{css-writer-BHjTF0YN.js → css-writer-CQYbT4g_.js} +3 -3
  14. package/dist/{css-writer-BHjTF0YN.js.map → css-writer-CQYbT4g_.js.map} +1 -1
  15. package/dist/{format-rules-Cb0YIjyS.js → format-rules-DdmLdntR.js} +11 -9
  16. package/dist/format-rules-DdmLdntR.js.map +1 -0
  17. package/dist/{hydrate-xnaB3SDm.js → hydrate-D2ivfS-B.js} +2 -2
  18. package/dist/{hydrate-xnaB3SDm.js.map → hydrate-D2ivfS-B.js.map} +1 -1
  19. package/dist/{index-DzGxoeyN.d.ts → index-DljBkZhV.d.ts} +59 -6
  20. package/dist/{index-KUYya3x7.d.ts → index-LC3O3Jj4.d.ts} +102 -2
  21. package/dist/index.d.ts +5 -5
  22. package/dist/index.js +62 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{keyframes-DiXjNoBZ.js → keyframes-CoLLjBcd.js} +2 -2
  25. package/dist/{keyframes-DiXjNoBZ.js.map → keyframes-CoLLjBcd.js.map} +1 -1
  26. package/dist/{merge-styles-DLm4wdnb.d.ts → merge-styles-Cx_6ySBv.d.ts} +2 -2
  27. package/dist/{merge-styles-B0lMso5W.js → merge-styles-bdh7-1uh.js} +2 -2
  28. package/dist/{merge-styles-B0lMso5W.js.map → merge-styles-bdh7-1uh.js.map} +1 -1
  29. package/dist/{resolve-recipes-C9nAuwgR.js → resolve-recipes-9JYSgubn.js} +3 -3
  30. package/dist/{resolve-recipes-C9nAuwgR.js.map → resolve-recipes-9JYSgubn.js.map} +1 -1
  31. package/dist/ssr/astro-client.js +1 -1
  32. package/dist/ssr/astro.js +3 -3
  33. package/dist/ssr/index.d.ts +1 -1
  34. package/dist/ssr/index.js +3 -3
  35. package/dist/ssr/next.d.ts +1 -1
  36. package/dist/ssr/next.js +4 -4
  37. package/dist/static/index.d.ts +2 -2
  38. package/dist/static/index.js +1 -1
  39. package/dist/zero/babel.d.ts +1 -1
  40. package/dist/zero/babel.js +4 -4
  41. package/dist/zero/index.d.ts +1 -1
  42. package/dist/zero/index.js +1 -1
  43. package/dist/zero/next.d.ts +1 -1
  44. package/docs/configuration.md +137 -6
  45. package/docs/dsl.md +99 -2
  46. package/docs/injector.md +11 -0
  47. package/docs/styles.md +3 -1
  48. package/package.json +6 -6
  49. package/dist/config-DOCuTykY.js.map +0 -1
  50. package/dist/core-p6iTbQnn.js.map +0 -1
  51. package/dist/format-rules-Cb0YIjyS.js.map +0 -1
@@ -18,6 +18,23 @@ const CSS_WIDE_KEYWORDS = new Set([
18
18
  "unset",
19
19
  "revert-layer"
20
20
  ]);
21
+ /**
22
+ * Color functions that *derive* a color from other colors. They take no alpha
23
+ * channel, so opacity has to wrap the whole call in `color-mix()` instead of
24
+ * being appended after a slash.
25
+ */
26
+ const DERIVED_COLOR_FUNCS_LIST = [
27
+ "color-mix",
28
+ "color-contrast",
29
+ "contrast-color",
30
+ "light-dark"
31
+ ];
32
+ const DERIVED_COLOR_FUNCS = new Set(DERIVED_COLOR_FUNCS_LIST);
33
+ /**
34
+ * Every color function the parser recognizes. The ones outside
35
+ * `DERIVED_COLOR_FUNCS` take channels as arguments, optionally followed by
36
+ * `/ <alpha>` — which is where an opacity suffix writes.
37
+ */
21
38
  const COLOR_FUNCS = new Set([
22
39
  "rgb",
23
40
  "rgba",
@@ -31,9 +48,20 @@ const COLOR_FUNCS = new Set([
31
48
  "color",
32
49
  "device-cmyk",
33
50
  "gray",
34
- "color-mix",
35
- "color-contrast"
51
+ ...DERIVED_COLOR_FUNCS_LIST
36
52
  ]);
53
+ /** A value that is a single `name(args)` call: captures the name and the args. */
54
+ const RE_FUNC_CALL = /^([a-z][a-z0-9-]*)\((.+)\)$/i;
55
+ /**
56
+ * The color function a value calls at its top level, lowercased — or `null` when
57
+ * the value is not a single color function call.
58
+ */
59
+ function colorFuncName(value) {
60
+ const match = value.match(RE_FUNC_CALL);
61
+ if (!match) return null;
62
+ const name = match[1].toLowerCase();
63
+ return COLOR_FUNCS.has(name) ? name : null;
64
+ }
37
65
  const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
38
66
  const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
39
67
  const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
@@ -110,141 +138,6 @@ function foldDslCase(src) {
110
138
  return out + src.slice(last).toLowerCase();
111
139
  }
112
140
  //#endregion
113
- //#region src/parser/lru.ts
114
- var Lru = class {
115
- limit;
116
- map = /* @__PURE__ */ new Map();
117
- head = null;
118
- tail = null;
119
- onEvict;
120
- constructor(limit = 1e3, onEvict) {
121
- this.limit = limit;
122
- let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
123
- if (normalized <= 0) normalized = 1e3;
124
- this.limit = normalized;
125
- this.onEvict = onEvict;
126
- }
127
- setOnEvict(fn) {
128
- this.onEvict = fn;
129
- }
130
- get(key) {
131
- const node = this.map.get(key);
132
- if (!node) return void 0;
133
- this.touch(key, node);
134
- return node.value;
135
- }
136
- set(key, value) {
137
- let node = this.map.get(key);
138
- if (node) {
139
- node.value = value;
140
- this.touch(key, node);
141
- return;
142
- }
143
- node = {
144
- prev: null,
145
- next: this.head,
146
- value
147
- };
148
- if (this.head) {
149
- const headNode = this.map.get(this.head);
150
- if (headNode) headNode.prev = key;
151
- }
152
- this.head = key;
153
- if (!this.tail) this.tail = key;
154
- this.map.set(key, node);
155
- if (this.map.size > this.limit) this.evict();
156
- }
157
- delete(key) {
158
- const node = this.map.get(key);
159
- if (!node) return;
160
- if (node.prev) {
161
- const prevNode = this.map.get(node.prev);
162
- if (prevNode) prevNode.next = node.next;
163
- }
164
- if (node.next) {
165
- const nextNode = this.map.get(node.next);
166
- if (nextNode) nextNode.prev = node.prev;
167
- }
168
- if (this.head === key) this.head = node.next;
169
- if (this.tail === key) this.tail = node.prev;
170
- this.map.delete(key);
171
- }
172
- keys() {
173
- return this.map.keys();
174
- }
175
- touch(key, node) {
176
- if (this.head === key) return;
177
- if (node.prev) {
178
- const prevNode = this.map.get(node.prev);
179
- if (prevNode) prevNode.next = node.next;
180
- }
181
- if (node.next) {
182
- const nextNode = this.map.get(node.next);
183
- if (nextNode) nextNode.prev = node.prev;
184
- }
185
- if (this.tail === key) this.tail = node.prev;
186
- node.prev = null;
187
- node.next = this.head;
188
- if (this.head) {
189
- const headNode = this.map.get(this.head);
190
- if (headNode) headNode.prev = key;
191
- }
192
- this.head = key;
193
- }
194
- evict() {
195
- const old = this.tail;
196
- if (!old) return;
197
- const node = this.map.get(old);
198
- if (!node) {
199
- if (this.head === old) this.head = null;
200
- this.tail = null;
201
- return;
202
- }
203
- if (node.prev) {
204
- const prevNode = this.map.get(node.prev);
205
- if (prevNode) prevNode.next = null;
206
- }
207
- this.tail = node.prev;
208
- if (this.head === old) this.head = null;
209
- this.map.delete(old);
210
- if (this.onEvict) try {
211
- this.onEvict(old, node.value);
212
- } catch {}
213
- }
214
- clear() {
215
- this.map.clear();
216
- this.head = this.tail = null;
217
- }
218
- };
219
- //#endregion
220
- //#region src/utils/function-color.ts
221
- const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
222
- const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
223
- /**
224
- * Resolve a `name(...)` value produced by a registered custom parse function
225
- * into its concrete color output.
226
- *
227
- * A color function is just a `functions` entry whose output is an already
228
- * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
229
- * value to the global parser (which already runs the registered parse function)
230
- * and returns the result only when it looks like a color. Returns `null` when
231
- * `str` is not a registered custom function or its output is not a color.
232
- *
233
- * This is the generic replacement for the previously hardcoded okhsl/okhst
234
- * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
235
- * the `#token.alpha` injection path.
236
- */
237
- function resolveFunctionColor(str) {
238
- const m = RE_FUNC_NAME.exec(str);
239
- if (!m) return null;
240
- const name = m[1].toLowerCase();
241
- getGlobalParser();
242
- if (!(name in getGlobalParseFunctions())) return null;
243
- const out = getGlobalParser().process(str).output;
244
- if (!out || !RE_COLOR_OUT.test(out)) return null;
245
- return out;
246
- }
247
- //#endregion
248
141
  //#region src/utils/color-math.ts
249
142
  const OKLab_to_LMS_M = [
250
143
  [
@@ -984,6 +877,141 @@ function oklchStringToRgb(oklchStr) {
984
877
  return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
985
878
  }
986
879
  //#endregion
880
+ //#region src/parser/lru.ts
881
+ var Lru = class {
882
+ limit;
883
+ map = /* @__PURE__ */ new Map();
884
+ head = null;
885
+ tail = null;
886
+ onEvict;
887
+ constructor(limit = 1e3, onEvict) {
888
+ this.limit = limit;
889
+ let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
890
+ if (normalized <= 0) normalized = 1e3;
891
+ this.limit = normalized;
892
+ this.onEvict = onEvict;
893
+ }
894
+ setOnEvict(fn) {
895
+ this.onEvict = fn;
896
+ }
897
+ get(key) {
898
+ const node = this.map.get(key);
899
+ if (!node) return void 0;
900
+ this.touch(key, node);
901
+ return node.value;
902
+ }
903
+ set(key, value) {
904
+ let node = this.map.get(key);
905
+ if (node) {
906
+ node.value = value;
907
+ this.touch(key, node);
908
+ return;
909
+ }
910
+ node = {
911
+ prev: null,
912
+ next: this.head,
913
+ value
914
+ };
915
+ if (this.head) {
916
+ const headNode = this.map.get(this.head);
917
+ if (headNode) headNode.prev = key;
918
+ }
919
+ this.head = key;
920
+ if (!this.tail) this.tail = key;
921
+ this.map.set(key, node);
922
+ if (this.map.size > this.limit) this.evict();
923
+ }
924
+ delete(key) {
925
+ const node = this.map.get(key);
926
+ if (!node) return;
927
+ if (node.prev) {
928
+ const prevNode = this.map.get(node.prev);
929
+ if (prevNode) prevNode.next = node.next;
930
+ }
931
+ if (node.next) {
932
+ const nextNode = this.map.get(node.next);
933
+ if (nextNode) nextNode.prev = node.prev;
934
+ }
935
+ if (this.head === key) this.head = node.next;
936
+ if (this.tail === key) this.tail = node.prev;
937
+ this.map.delete(key);
938
+ }
939
+ keys() {
940
+ return this.map.keys();
941
+ }
942
+ touch(key, node) {
943
+ if (this.head === key) return;
944
+ if (node.prev) {
945
+ const prevNode = this.map.get(node.prev);
946
+ if (prevNode) prevNode.next = node.next;
947
+ }
948
+ if (node.next) {
949
+ const nextNode = this.map.get(node.next);
950
+ if (nextNode) nextNode.prev = node.prev;
951
+ }
952
+ if (this.tail === key) this.tail = node.prev;
953
+ node.prev = null;
954
+ node.next = this.head;
955
+ if (this.head) {
956
+ const headNode = this.map.get(this.head);
957
+ if (headNode) headNode.prev = key;
958
+ }
959
+ this.head = key;
960
+ }
961
+ evict() {
962
+ const old = this.tail;
963
+ if (!old) return;
964
+ const node = this.map.get(old);
965
+ if (!node) {
966
+ if (this.head === old) this.head = null;
967
+ this.tail = null;
968
+ return;
969
+ }
970
+ if (node.prev) {
971
+ const prevNode = this.map.get(node.prev);
972
+ if (prevNode) prevNode.next = null;
973
+ }
974
+ this.tail = node.prev;
975
+ if (this.head === old) this.head = null;
976
+ this.map.delete(old);
977
+ if (this.onEvict) try {
978
+ this.onEvict(old, node.value);
979
+ } catch {}
980
+ }
981
+ clear() {
982
+ this.map.clear();
983
+ this.head = this.tail = null;
984
+ }
985
+ };
986
+ //#endregion
987
+ //#region src/utils/function-color.ts
988
+ const RE_FUNC_NAME = /^([a-z][a-z0-9-]*)\s*\(/i;
989
+ const RE_COLOR_OUT = /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color)\(|^#|^var\(--/i;
990
+ /**
991
+ * Resolve a `name(...)` value produced by a registered custom parse function
992
+ * into its concrete color output.
993
+ *
994
+ * A color function is just a `functions` entry whose output is an already
995
+ * supported color (`rgb`, `hsl`, `#…`, `oklch`, …). This helper delegates the
996
+ * value to the global parser (which already runs the registered parse function)
997
+ * and returns the result only when it looks like a color. Returns `null` when
998
+ * `str` is not a registered custom function or its output is not a color.
999
+ *
1000
+ * This is the generic replacement for the previously hardcoded okhsl/okhst
1001
+ * conversion branches scattered across `strToRgb`, `resolveToRgbaValues`, and
1002
+ * the `#token.alpha` injection path.
1003
+ */
1004
+ function resolveFunctionColor(str) {
1005
+ const m = RE_FUNC_NAME.exec(str);
1006
+ if (!m) return null;
1007
+ const name = m[1].toLowerCase();
1008
+ getGlobalParser();
1009
+ if (!(name in getGlobalParseFunctions())) return null;
1010
+ const out = getGlobalParser().process(str).output;
1011
+ if (!out || !RE_COLOR_OUT.test(out)) return null;
1012
+ return out;
1013
+ }
1014
+ //#endregion
987
1015
  //#region src/utils/color-space.ts
988
1016
  let currentColorSpace = "oklch";
989
1017
  const colorSpaceCache = new Lru(500);
@@ -1003,9 +1031,6 @@ function resetColorSpace() {
1003
1031
  function getColorSpaceSuffix() {
1004
1032
  return currentColorSpace;
1005
1033
  }
1006
- function getColorSpaceFunc() {
1007
- return currentColorSpace;
1008
- }
1009
1034
  function formatNum(n, precision) {
1010
1035
  return parseFloat(n.toFixed(precision)).toString();
1011
1036
  }
@@ -1262,6 +1287,91 @@ function strToColorSpace(color) {
1262
1287
  return result;
1263
1288
  }
1264
1289
  /**
1290
+ * Set the alpha of a whole color, replacing any it already carries.
1291
+ *
1292
+ * CSS relative color syntax is how opacity is applied to every color Tasty
1293
+ * emits. `oklch(from <color> l c h / <alpha>)` copies the channels over and
1294
+ * writes the alpha slot, which needs nothing from the color except that it *is*
1295
+ * a color: a `color-mix()`, a `light-dark()`, a `currentcolor`, a
1296
+ * `--name-color` written by hand-authored CSS with no companion variable — all
1297
+ * of them work, where writing into a channel slot directly requires components
1298
+ * the engine may have no way to compute.
1299
+ *
1300
+ * Alpha is *replaced*, not composed. A token holding `rgb(255 0 0 / .8)` faded
1301
+ * to `.5` is alpha `.5`, matching what the channel-components form did and what
1302
+ * a statically-known color still does. `color-mix()` against `transparent`
1303
+ * cannot do this — it would multiply the two to `.4`.
1304
+ *
1305
+ * The alpha slot takes a `<number>` or a `<percentage>`, so an opacity custom
1306
+ * property passes straight through in whichever form the author declared it.
1307
+ *
1308
+ * The space is always `oklch`, regardless of the configured {@link ColorSpace}:
1309
+ * it is unbounded, so a wide-gamut color survives the round trip that a
1310
+ * gamut-limited space would clamp, and it is the space the computed value used
1311
+ * to be reported in. Channels are copied rather than interpolated, so the polar
1312
+ * form costs nothing even for an achromatic color, whose hue is carried through
1313
+ * untouched.
1314
+ */
1315
+ function overrideColorAlpha(color, alpha) {
1316
+ return `oklch(from ${color} l c h / ${alpha})`;
1317
+ }
1318
+ /**
1319
+ * Compose an alpha onto a color, multiplying with any it already carries.
1320
+ *
1321
+ * This is the counterpart to {@link overrideColorAlpha}, and the difference is
1322
+ * the point. A design token names a color, so fading it *sets* its alpha. But
1323
+ * `currentcolor` is the color an element **inherits**, which an ancestor may
1324
+ * already have faded — `#current.4` there means "40% of what reaches me", and a
1325
+ * nested `#current.18` under it composes to `.072`. Design systems build ramps
1326
+ * out of exactly that, so `#current` composes and a token replaces.
1327
+ *
1328
+ * `color-mix()` against `transparent` is what composes: mixing premultiplied
1329
+ * leaves the channels alone and multiplies the alphas. Its percentage slot takes
1330
+ * no `<number>`, so a `$prop` alpha has to be scaled — which is why an opacity
1331
+ * property used as `#current.$prop` has to hold a unitless number, where a token
1332
+ * accepts either form.
1333
+ */
1334
+ function mixColorAlpha(color, percentage) {
1335
+ return `color-mix(in oklab, ${color} ${percentage}, transparent)`;
1336
+ }
1337
+ /**
1338
+ * Matches what {@link overrideColorAlpha} builds. The first group is greedy so a
1339
+ * nested override splits on its outermost layer.
1340
+ */
1341
+ const RE_ALPHA_OVERRIDE = /^oklch\(from (.+) l c h \/ (.+)\)$/;
1342
+ /**
1343
+ * Split what {@link overrideColorAlpha} builds back into the color it faded and
1344
+ * the alpha, or `null` when the value is not one of those.
1345
+ */
1346
+ function parseAlphaOverride(value) {
1347
+ const match = value.match(RE_ALPHA_OVERRIDE);
1348
+ return match ? {
1349
+ color: match[1],
1350
+ alpha: match[2]
1351
+ } : null;
1352
+ }
1353
+ /** Channel names of each color space, in `<func>()` argument order. */
1354
+ const COLOR_SPACE_CHANNELS = {
1355
+ rgb: "r g b",
1356
+ hsl: "h s l",
1357
+ oklch: "l c h"
1358
+ };
1359
+ /**
1360
+ * Express a color as components of the configured color space *by reference*,
1361
+ * using CSS relative color syntax: `from <color> l c h`.
1362
+ *
1363
+ * Static colors are decomposed into numbers (see `getColorSpaceComponents`), but
1364
+ * a color the engine cannot evaluate at build time — a `color-mix()`, a
1365
+ * `light-dark()`, a `color()` in a space with no conversion, anything reached
1366
+ * through `var()` — has no numbers to decompose. Handing back the relative form
1367
+ * keeps the `--name-color-{space}` companion usable anyway: the browser resolves
1368
+ * the channels, so `oklch(var(--name-color-oklch) / .5)` still applies opacity to
1369
+ * whatever the color turns out to be.
1370
+ */
1371
+ function toRelativeColorSpaceComponents(color) {
1372
+ return `from ${color} ${COLOR_SPACE_CHANNELS[currentColorSpace]}`;
1373
+ }
1374
+ /**
1265
1375
  * Extract the decomposed components of a color in the configured color space.
1266
1376
  * Returns a space-separated string of components without the wrapping function.
1267
1377
  * Alpha is NOT included — components are used for alpha composition via `/ alpha`.
@@ -1282,6 +1392,41 @@ function getColorSpaceComponents(color) {
1282
1392
  return result;
1283
1393
  }
1284
1394
  /**
1395
+ * Rewrite a color into the `--name-color-{space}` components that `#name.alpha`
1396
+ * composes opacity onto.
1397
+ *
1398
+ * A `var()` chain is rewritten reference by reference so the fallback order
1399
+ * survives; a color the engine can evaluate is decomposed into numbers; anything
1400
+ * left — a derived color function, a `color()` in a space with no conversion —
1401
+ * falls back to relative color syntax and lets the browser do it.
1402
+ *
1403
+ * Returns the input unchanged when there is nothing to rewrite, so callers can
1404
+ * tell whether the conversion found anything.
1405
+ *
1406
+ * Example: `var(--primary-color, var(--secondary-color))`
1407
+ * → `var(--primary-color-oklch, var(--secondary-color-oklch))`
1408
+ */
1409
+ function convertColorChainToComponentChain(colorValue) {
1410
+ const suffix = getColorSpaceSuffix();
1411
+ const faded = parseAlphaOverride(colorValue);
1412
+ if (faded) return convertColorChainToComponentChain(faded.color);
1413
+ const componentVarMatch = colorValue.match(/^(?:rgb|hsl|oklch)a?\(\s*(var\(--[a-z0-9-]+-color-(?:rgb|hsl|oklch)\))\s*\//);
1414
+ if (componentVarMatch) return componentVarMatch[1];
1415
+ if (colorFuncName(colorValue)) {
1416
+ const components = getColorSpaceComponents(colorValue);
1417
+ return components !== colorValue ? components : toRelativeColorSpaceComponents(colorValue);
1418
+ }
1419
+ const match = colorValue.match(/var\(--([a-z0-9-]+)-color\s*(?:,\s*(.+))?\)/);
1420
+ if (!match) {
1421
+ const components = getColorSpaceComponents(colorValue);
1422
+ if (components !== colorValue) return components;
1423
+ return colorValue;
1424
+ }
1425
+ const [, name, fallback] = match;
1426
+ if (!fallback) return `var(--${name}-color-${suffix})`;
1427
+ return `var(--${name}-color-${suffix}, ${convertColorChainToComponentChain(fallback.trim())})`;
1428
+ }
1429
+ /**
1285
1430
  * Convert a color initial value (from @property definitions) to components
1286
1431
  * in the configured color space.
1287
1432
  */
@@ -1354,6 +1499,53 @@ const finalizeGroup = (d, parts) => {
1354
1499
  //#endregion
1355
1500
  //#region src/parser/classify.ts
1356
1501
  /**
1502
+ * Convert an opacity suffix to the alpha value it denotes.
1503
+ *
1504
+ * The authored digits are kept verbatim — `.07` stays `.07` rather than being
1505
+ * multiplied into `7.000000000000001%` — and a `$prop` suffix passes the
1506
+ * reference straight through, so it works whether the property holds a
1507
+ * `<number>` or a `<percentage>`. Both are what the alpha slot accepts.
1508
+ */
1509
+ function alphaSuffixToAlpha(rawAlpha) {
1510
+ if (rawAlpha.startsWith("$")) return `var(--${rawAlpha.slice(1)})`;
1511
+ if (rawAlpha === "0") return "0";
1512
+ return `.${rawAlpha}`;
1513
+ }
1514
+ /** Apply an opacity suffix to a color, replacing any alpha it already carries. */
1515
+ function fadeColor(color, rawAlpha) {
1516
+ return overrideColorAlpha(color, alphaSuffixToAlpha(rawAlpha));
1517
+ }
1518
+ /**
1519
+ * Convert an opacity suffix to the percentage `color-mix()` needs, by shifting
1520
+ * the decimal point rather than multiplying: `.07` is `7%`, not the
1521
+ * `7.000000000000001%` that `parseFloat('.07') * 100` produces.
1522
+ */
1523
+ function alphaSuffixToPercentage(rawAlpha) {
1524
+ if (rawAlpha.startsWith("$")) return `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
1525
+ if (rawAlpha === "0") return "0%";
1526
+ const digits = rawAlpha.length === 1 ? `${rawAlpha}0` : rawAlpha;
1527
+ const whole = String(Number(digits.slice(0, 2)));
1528
+ const fraction = digits.slice(2);
1529
+ return `${fraction ? `${whole}.${fraction}` : whole}%`;
1530
+ }
1531
+ /**
1532
+ * Apply an opacity suffix to `currentcolor`, composing with any alpha an
1533
+ * ancestor already applied. See {@link mixColorAlpha} for why this differs from
1534
+ * how a token is faded.
1535
+ */
1536
+ function fadeCurrentColor(rawAlpha) {
1537
+ return mixColorAlpha("currentcolor", alphaSuffixToPercentage(rawAlpha));
1538
+ }
1539
+ /**
1540
+ * Whether parsed function arguments hold a color. Colors reach either the color
1541
+ * bucket (`#token`, a nested color function, `transparent`) or — for the CSS
1542
+ * named colors, which the parser has no token syntax for — the modifier bucket.
1543
+ */
1544
+ function hasColorArgs(parsed) {
1545
+ const namedColors = getNamedColorHex();
1546
+ return parsed.groups.some((group) => group.colors.length > 0 || group.mods.some((mod) => namedColors.has(mod)));
1547
+ }
1548
+ /**
1357
1549
  * Re-parses a value through the parser until it stabilizes (no changes)
1358
1550
  * or max iterations reached. This allows units to reference other units.
1359
1551
  * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'
@@ -1455,17 +1647,10 @@ function classify(raw, opts, recurse) {
1455
1647
  processed: "currentcolor"
1456
1648
  };
1457
1649
  const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1458
- if (currentAlphaMatch) {
1459
- const rawAlpha = currentAlphaMatch[1];
1460
- let percentage;
1461
- if (rawAlpha.startsWith("$")) percentage = `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
1462
- else if (rawAlpha === "0") percentage = "0%";
1463
- else percentage = `${parseFloat("." + rawAlpha) * 100}%`;
1464
- return {
1465
- bucket: 0,
1466
- processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`
1467
- };
1468
- }
1650
+ if (currentAlphaMatch) return {
1651
+ bucket: 0,
1652
+ processed: fadeCurrentColor(currentAlphaMatch[1])
1653
+ };
1469
1654
  if (token[0] === "$" || token[0] === "#") {
1470
1655
  const predefinedTokens = getGlobalPredefinedTokens();
1471
1656
  if (predefinedTokens) {
@@ -1482,10 +1667,14 @@ function classify(raw, opts, recurse) {
1482
1667
  if (baseKey in predefinedTokens) {
1483
1668
  const resolvedValue = predefinedTokens[baseKey];
1484
1669
  if (resolvedValue.startsWith("#")) return classify(`${foldDslCase(resolvedValue)}.${rawAlpha}`, opts, recurse);
1485
- const funcMatch = resolvedValue.match(/^([a-z][a-z0-9-]*)\((.+)\)$/i);
1670
+ const funcMatch = resolvedValue.match(RE_FUNC_CALL);
1486
1671
  if (funcMatch) {
1487
1672
  const [, funcName, args] = funcMatch;
1488
1673
  const lowerFunc = funcName.toLowerCase();
1674
+ if (DERIVED_COLOR_FUNCS.has(lowerFunc)) return {
1675
+ bucket: 0,
1676
+ processed: fadeColor(classify(foldDslCase(resolvedValue), opts, recurse).processed, rawAlpha)
1677
+ };
1489
1678
  const isCustomFunc = !!(opts.functions && lowerFunc in opts.functions && !COLOR_FUNCS.has(lowerFunc) && !COLOR_FUNCS.has(funcName.replace(/a$/i, "").toLowerCase()));
1490
1679
  const normalizedFunc = isCustomFunc ? lowerFunc : funcName.replace(/a$/i, "").toLowerCase();
1491
1680
  if (!(COLOR_FUNCS.has(normalizedFunc) || COLOR_FUNCS.has(lowerFunc) || isCustomFunc)) return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
@@ -1573,13 +1762,9 @@ function classify(raw, opts, recurse) {
1573
1762
  const alphaMatch = token.match(/^#([a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
1574
1763
  if (alphaMatch) {
1575
1764
  const [, base, rawAlpha] = alphaMatch;
1576
- let alpha;
1577
- if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
1578
- else if (rawAlpha === "0") alpha = "0";
1579
- else alpha = `.${rawAlpha}`;
1580
1765
  return {
1581
1766
  bucket: 0,
1582
- processed: `${getColorSpaceFunc()}(var(--${base}-color-${getColorSpaceSuffix()}) / ${alpha})`
1767
+ processed: fadeColor(`var(--${base}-color)`, rawAlpha)
1583
1768
  };
1584
1769
  }
1585
1770
  const name = token.slice(1);
@@ -1597,10 +1782,12 @@ function classify(raw, opts, recurse) {
1597
1782
  const fname = token.slice(0, openIdx);
1598
1783
  const inner = token.slice(openIdx + 1, -1);
1599
1784
  if (COLOR_FUNCS.has(fname)) {
1600
- const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
1785
+ const parsedInner = recurse(inner);
1786
+ const argProcessed = parsedInner.output.replace(/,\s+/g, ",");
1787
+ const processed = `${canonicalFuncName(fname)}(${argProcessed})`;
1601
1788
  return {
1602
- bucket: 0,
1603
- processed: `${canonicalFuncName(fname)}(${argProcessed})`
1789
+ bucket: fname === "light-dark" && !hasColorArgs(parsedInner) ? 1 : 0,
1790
+ processed
1604
1791
  };
1605
1792
  }
1606
1793
  if (opts.functions && fname in opts.functions) {
@@ -2076,8 +2263,8 @@ function normalizeColorTokenValue(value) {
2076
2263
  if (value === false) return null;
2077
2264
  return value;
2078
2265
  }
2079
- const COLOR_VAR_PATTERN = /var\(--([a-z0-9-]+)-color/;
2080
- const COLOR_VAR_COMPONENTS_PATTERN = /var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;
2266
+ const COLOR_VAR_PATTERN = /^var\(--([a-z0-9-]+)-color[,)]/;
2267
+ const COLOR_VAR_COMPONENTS_PATTERN = /^(?:[a-z-]+\(\s*)?var\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)[,)]/;
2081
2268
  const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
2082
2269
  const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
2083
2270
  const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
@@ -2267,11 +2454,17 @@ function parseColor(val, ignoreError = false) {
2267
2454
  }
2268
2455
  firstColor = extractedColor;
2269
2456
  }
2270
- let nameMatch = firstColor.match(COLOR_VAR_PATTERN);
2271
- if (!nameMatch) nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2457
+ const faded = parseAlphaOverride(firstColor);
2458
+ const baseColor = faded ? faded.color : firstColor;
2459
+ let nameMatch = baseColor.match(COLOR_VAR_PATTERN);
2460
+ if (!nameMatch) nameMatch = baseColor.match(COLOR_VAR_COMPONENTS_PATTERN);
2272
2461
  let opacity;
2273
- if (firstColor.startsWith("rgb") || firstColor.startsWith("hsl") || firstColor.startsWith("lch") || firstColor.startsWith("oklch")) {
2274
- const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);
2462
+ if (faded) {
2463
+ const { alpha } = faded;
2464
+ const v = parseFloat(alpha);
2465
+ if (!isNaN(v)) opacity = alpha.endsWith("%") ? v : v * 100;
2466
+ } else if (baseColor.startsWith("rgb") || baseColor.startsWith("hsl") || baseColor.startsWith("lch") || baseColor.startsWith("oklch")) {
2467
+ const alphaMatch = baseColor.match(RGB_ALPHA_PATTERN);
2275
2468
  if (alphaMatch) {
2276
2469
  const v = parseFloat(alphaMatch[1]);
2277
2470
  if (!isNaN(v)) opacity = v * 100;
@@ -3230,6 +3423,217 @@ function rscClassRegexGlobal(prefix) {
3230
3423
  return new RegExp(`\\.(${escapeRegex(prefix)}[a-z0-9]+)\\.\\1`, "g");
3231
3424
  }
3232
3425
  //#endregion
3426
+ //#region src/utils/warnings.ts
3427
+ const PREFIX = "[Tasty]";
3428
+ function warn(...args) {
3429
+ console.warn(PREFIX, ...args);
3430
+ }
3431
+ //#endregion
3432
+ //#region src/injector/batch.ts
3433
+ /**
3434
+ * Deferred CSSOM writes ("batched injection").
3435
+ *
3436
+ * Every `insertRule()` on a live stylesheet invalidates style for the sheet's
3437
+ * scope, so Blink recalculates style the next time anything reads layout or
3438
+ * computed style. When components inject during React's render phase and other
3439
+ * components read layout in the same pass, the two interleave:
3440
+ *
3441
+ * inject -> read (forced recalc) -> inject -> read (forced recalc) -> ...
3442
+ *
3443
+ * Batching moves every sheet write out from between those reads. Writes are
3444
+ * queued in FIFO order and drained in one go, so the tree is invalidated once
3445
+ * per flush instead of once per component.
3446
+ *
3447
+ * ## Ordering
3448
+ *
3449
+ * A single queue holds *all* writes — component rules, global rules, keyframes,
3450
+ * `@property`, `@font-face`, `@counter-style`, `@function` and raw CSS. Draining
3451
+ * it in insertion order keeps the sheet byte-identical to unbatched output,
3452
+ * which matters because equal-specificity rules resolve by document order.
3453
+ *
3454
+ * ## Batch windows — why queuing is safe
3455
+ *
3456
+ * Deferring a write past React's layout phase would let a `useLayoutEffect`
3457
+ * measure an element whose rules are not in the sheet yet, reading the unstyled
3458
+ * box. `<TastyBatchProvider>` closes that hole by opening a *window* during its
3459
+ * render and closing it — flushing — in its `useInsertionEffect`, which React
3460
+ * runs in the mutation phase, before any layout effect:
3461
+ *
3462
+ * provider renders -> window OPEN
3463
+ * children render -> injections queued
3464
+ * provider insertionEffect -> FLUSH, window CLOSED
3465
+ * layout effects run -> rules are in the sheet
3466
+ *
3467
+ * So in the default (`batchInjection: true`) mode a write is only ever queued
3468
+ * inside a commit whose flush is already guaranteed by that same commit. Every
3469
+ * injection outside a window — a deep update the provider did not re-render
3470
+ * for, a `useLayoutEffect` that injects, an event handler, an async callback —
3471
+ * is written straight through, exactly as with batching off.
3472
+ *
3473
+ * `batchInjection: 'always'` opts out of the gate and queues unconditionally.
3474
+ * That wins on more commits, at the cost of the measurement hazard above.
3475
+ *
3476
+ * ## Flush points (earliest wins)
3477
+ *
3478
+ * 1. `<TastyBatchProvider>`'s `useInsertionEffect` — closes the window.
3479
+ * 2. A microtask — a backstop for a render that was aborted or suspended and
3480
+ * therefore never reached its insertion effect. An aborted render mounts
3481
+ * nothing, so nothing can measure what it queued. Microtasks also always
3482
+ * drain before paint, so styles are never visually missing.
3483
+ * 3. `flushStyles()` — explicit, and called internally by every injector read
3484
+ * API (`getCSSText`, `cleanup`, `gc`, `destroy`, ...).
3485
+ */
3486
+ /**
3487
+ * FIFO of pending writes. Drained with a moving `head` index rather than
3488
+ * `shift()` so a large batch stays O(n) instead of O(n²).
3489
+ */
3490
+ let queue = [];
3491
+ let head = 0;
3492
+ let microtaskScheduled = false;
3493
+ let flushing = false;
3494
+ /**
3495
+ * Whether a `<TastyBatchProvider>` has rendered in the current commit and will
3496
+ * therefore flush in its insertion effect, making it safe to queue a write.
3497
+ *
3498
+ * A flag rather than a depth count, because renders and insertion effects do
3499
+ * not pair up one-to-one. StrictMode double-invokes render but runs the
3500
+ * insertion effect once, so a counter ends the commit stuck above zero — and a
3501
+ * window that stays open past its commit turns the next provider-less commit
3502
+ * into `'always'` mode behind the user's back, which is exactly the measurement
3503
+ * hazard `true` exists to avoid.
3504
+ *
3505
+ * Clearing on the first close is safe with nested or sibling providers: React
3506
+ * finishes every render in a commit before running any insertion effect, so once
3507
+ * one closes, no further render-phase injection can arrive in that commit.
3508
+ */
3509
+ let windowOpen = false;
3510
+ /** Whether any window has ever been opened, i.e. a provider is in the tree. */
3511
+ let everOpened = false;
3512
+ /** Dev-only: warn at most once that batching is on with no provider mounted. */
3513
+ let warnedNoProvider = false;
3514
+ /**
3515
+ * Queue a sheet write.
3516
+ *
3517
+ * Returns a handle whose `cancel()` drops the write if the caller disposes
3518
+ * before the flush. The handle is intentionally tiny — one is allocated per
3519
+ * injected class, on the cache-miss path only.
3520
+ */
3521
+ function enqueueStyleWrite(run) {
3522
+ if (flushing) {
3523
+ run();
3524
+ return {
3525
+ run,
3526
+ cancelled: false,
3527
+ done: true
3528
+ };
3529
+ }
3530
+ const entry = {
3531
+ run,
3532
+ cancelled: false,
3533
+ done: false
3534
+ };
3535
+ queue.push(entry);
3536
+ scheduleMicrotaskFlush();
3537
+ return entry;
3538
+ }
3539
+ /** Whether any write is still waiting to hit a stylesheet. */
3540
+ function hasPendingStyleWrites() {
3541
+ return head < queue.length;
3542
+ }
3543
+ /**
3544
+ * Open a batch window. Called from `<TastyBatchProvider>`'s render, so every
3545
+ * descendant injection in this commit is covered by the provider's insertion
3546
+ * effect.
3547
+ *
3548
+ * Called during render on purpose: the window must be open before children
3549
+ * render. It is idempotent — so StrictMode's double render costs nothing — and
3550
+ * carries no other side effect. A render that is thrown away is recovered by the
3551
+ * microtask backstop.
3552
+ *
3553
+ * A no-op without a `document`. On the server there is no sheet to batch
3554
+ * against, and `useInsertionEffect` never runs, so nothing would ever close a
3555
+ * window this opened. Skipping it keeps the provider inert during SSR and RSC
3556
+ * instead of merely harmless.
3557
+ */
3558
+ function openBatchWindow() {
3559
+ if (typeof document === "undefined") return;
3560
+ windowOpen = true;
3561
+ everOpened = true;
3562
+ }
3563
+ /**
3564
+ * Close a batch window and flush. Called from the provider's
3565
+ * `useInsertionEffect`, i.e. after every render in the commit and before any
3566
+ * layout effect.
3567
+ */
3568
+ function closeBatchWindow() {
3569
+ windowOpen = false;
3570
+ flushStyles();
3571
+ }
3572
+ /** Whether a batch window is currently open. */
3573
+ function isBatchWindowOpen() {
3574
+ return windowOpen;
3575
+ }
3576
+ function scheduleMicrotaskFlush() {
3577
+ if (microtaskScheduled || flushing) return;
3578
+ microtaskScheduled = true;
3579
+ queueMicrotask(() => {
3580
+ microtaskScheduled = false;
3581
+ windowOpen = false;
3582
+ flushStyles();
3583
+ });
3584
+ }
3585
+ /**
3586
+ * Drain every pending sheet write, in insertion order.
3587
+ *
3588
+ * Safe to call when the queue is empty (the common case, so the guard comes
3589
+ * first) and safe to call re-entrantly: a nested `flushStyles()` is a no-op,
3590
+ * and work a draining write triggers is written in place by
3591
+ * `enqueueStyleWrite` rather than queued behind the rest of the batch.
3592
+ */
3593
+ function flushStyles() {
3594
+ if (head >= queue.length) return;
3595
+ if (flushing) return;
3596
+ flushing = true;
3597
+ try {
3598
+ while (head < queue.length) {
3599
+ const entry = queue[head++];
3600
+ if (entry.cancelled) {
3601
+ entry.done = true;
3602
+ continue;
3603
+ }
3604
+ entry.run();
3605
+ entry.done = true;
3606
+ }
3607
+ } finally {
3608
+ queue = [];
3609
+ head = 0;
3610
+ flushing = false;
3611
+ }
3612
+ }
3613
+ /**
3614
+ * Dev-only: `batchInjection: true` batches nothing unless a window opens, and no
3615
+ * window has ever opened, so no provider is in the tree. Called by the injector
3616
+ * the first time it declines to batch.
3617
+ */
3618
+ function warnBatchProviderMissing() {
3619
+ if (everOpened || warnedNoProvider || !isDevEnv()) return;
3620
+ warnedNoProvider = true;
3621
+ warn("[Tasty] batchInjection needs <TastyBatchProvider> mounted to batch.");
3622
+ }
3623
+ /**
3624
+ * Drop every pending write without applying it. Test helper — production code
3625
+ * should call `flushStyles()` instead.
3626
+ */
3627
+ function resetStyleBatch() {
3628
+ queue = [];
3629
+ head = 0;
3630
+ microtaskScheduled = false;
3631
+ flushing = false;
3632
+ windowOpen = false;
3633
+ everOpened = false;
3634
+ warnedNoProvider = false;
3635
+ }
3636
+ //#endregion
3233
3637
  //#region src/properties/property-type-resolver.ts
3234
3638
  /**
3235
3639
  * PropertyTypeResolver
@@ -3239,6 +3643,10 @@ function rscClassRegexGlobal(prefix) {
3239
3643
  */
3240
3644
  const CUSTOM_PROP_DECL = /^\s*(--[a-z0-9_-]+)\s*:\s*(.+?)\s*$/i;
3241
3645
  const SINGLE_VAR_REF = /^var\((--[a-z0-9_-]+)\)$/i;
3646
+ /** A `--name-color-{space}` companion holding a color's channel components. */
3647
+ const COLOR_COMPONENTS_DECL = /^\s*(--[a-z0-9_-]+-color-(?:rgb|hsl|oklch))\s*:\s*(.+?)\s*$/i;
3648
+ /** A component list the browser can type as `<number>+`. */
3649
+ const NUMERIC_COMPONENTS = /^[\d\s.+-]+$/;
3242
3650
  var PropertyTypeResolver = class {
3243
3651
  /** propName → the prop it depends on */
3244
3652
  pendingDeps = /* @__PURE__ */ new Map();
@@ -3251,6 +3659,15 @@ var PropertyTypeResolver = class {
3251
3659
  scanDeclarations(declarations, isPropertyDefined, registerProperty) {
3252
3660
  if (!declarations.includes("--")) return;
3253
3661
  const parts = declarations.split(/;+/);
3662
+ for (const part of parts) {
3663
+ const match = COLOR_COMPONENTS_DECL.exec(part);
3664
+ if (!match) continue;
3665
+ const propName = match[1];
3666
+ if (isPropertyDefined(propName)) continue;
3667
+ if (NUMERIC_COMPONENTS.test(match[2])) continue;
3668
+ if (isPropertyDefined(propName.slice(0, propName.lastIndexOf("-")))) continue;
3669
+ registerProperty(propName, "*", getDefaultComponents());
3670
+ }
3254
3671
  for (const part of parts) {
3255
3672
  if (!part.trim()) continue;
3256
3673
  const match = CUSTOM_PROP_DECL.exec(part);
@@ -3553,27 +3970,28 @@ function borderStyle({ border }) {
3553
3970
  }
3554
3971
  borderStyle.__lookupStyles = ["border"];
3555
3972
  //#endregion
3556
- //#region src/styles/createStyle.ts
3557
- const CACHE = {};
3558
- /**
3559
- * Convert color fallback chain to component fallback chain.
3560
- * Example: var(--primary-color, var(--secondary-color))
3561
- * → var(--primary-color-oklch, var(--secondary-color-oklch))
3562
- */
3563
- function convertColorChainToComponentChain(colorValue) {
3564
- const suffix = getColorSpaceSuffix();
3565
- const componentVarMatch = colorValue.match(/^(?:rgb|hsl|oklch)a?\(\s*(var\(--[a-z0-9-]+-color-(?:rgb|hsl|oklch)\))\s*\//);
3566
- if (componentVarMatch) return componentVarMatch[1];
3567
- const match = colorValue.match(/var\(--([a-z0-9-]+)-color\s*(?:,\s*(.+))?\)/);
3568
- if (!match) {
3569
- const components = getColorSpaceComponents(colorValue);
3570
- if (components !== colorValue) return components;
3571
- return colorValue;
3973
+ //#region src/styles/color.ts
3974
+ function colorStyle({ color }) {
3975
+ if (!color) return null;
3976
+ if (color === true) color = "currentColor";
3977
+ color = parseColor(color, true).color || resolveCustomProperties(color);
3978
+ const match = color.match(/var\(--(.+?)-color/);
3979
+ let name = "";
3980
+ if (match) name = match[1];
3981
+ const styles = { color };
3982
+ if (name && name !== "current") {
3983
+ const suffix = getColorSpaceSuffix();
3984
+ Object.assign(styles, {
3985
+ "--current-color": color,
3986
+ [`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
3987
+ });
3572
3988
  }
3573
- const [, name, fallback] = match;
3574
- if (!fallback) return `var(--${name}-color-${suffix})`;
3575
- return `var(--${name}-color-${suffix}, ${convertColorChainToComponentChain(fallback.trim())})`;
3989
+ return styles;
3576
3990
  }
3991
+ colorStyle.__lookupStyles = ["color"];
3992
+ //#endregion
3993
+ //#region src/styles/createStyle.ts
3994
+ const CACHE = {};
3577
3995
  function createStyle(styleName, cssStyle, converter) {
3578
3996
  const key = `${styleName}.${cssStyle ?? ""}`;
3579
3997
  if (!CACHE[key]) {
@@ -3617,6 +4035,10 @@ function createStyle(styleName, cssStyle, converter) {
3617
4035
  [finalCssStyle]: colorSpaceStr,
3618
4036
  [`${finalCssStyle}-${suffix}`]: getColorSpaceComponents(colorSpaceStr)
3619
4037
  };
4038
+ if (color && colorFuncName(color)) return {
4039
+ [finalCssStyle]: color,
4040
+ [`${finalCssStyle}-${suffix}`]: convertColorChainToComponentChain(color)
4041
+ };
3620
4042
  return { [finalCssStyle]: color ?? "" };
3621
4043
  }
3622
4044
  const processed = parseStyle(styleValue);
@@ -3628,27 +4050,6 @@ function createStyle(styleName, cssStyle, converter) {
3628
4050
  return CACHE[key];
3629
4051
  }
3630
4052
  //#endregion
3631
- //#region src/styles/color.ts
3632
- function colorStyle({ color }) {
3633
- if (!color) return null;
3634
- if (color === true) color = "currentColor";
3635
- if (typeof color === "string" && (color.startsWith("#") || color.startsWith("(#"))) color = parseColor(color).color || color;
3636
- else if (typeof color === "string") color = resolveCustomProperties(color);
3637
- const match = color.match(/var\(--(.+?)-color/);
3638
- let name = "";
3639
- if (match) name = match[1];
3640
- const styles = { color };
3641
- if (name && name !== "current") {
3642
- const suffix = getColorSpaceSuffix();
3643
- Object.assign(styles, {
3644
- "--current-color": color,
3645
- [`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
3646
- });
3647
- }
3648
- return styles;
3649
- }
3650
- colorStyle.__lookupStyles = ["color"];
3651
- //#endregion
3652
4053
  //#region src/styles/display.ts
3653
4054
  /**
3654
4055
  * Process textOverflow into CSS properties for truncation/clamping.
@@ -4587,12 +4988,6 @@ scrollMarginStyle.__lookupStyles = [
4587
4988
  "scrollMarginInline"
4588
4989
  ];
4589
4990
  //#endregion
4590
- //#region src/utils/warnings.ts
4591
- const PREFIX = "[Tasty]";
4592
- function warn(...args) {
4593
- console.warn(PREFIX, ...args);
4594
- }
4595
- //#endregion
4596
4991
  //#region src/styles/scrollbar.ts
4597
4992
  /**
4598
4993
  * Standard CSS scrollbar styling via `scrollbar-width`, `scrollbar-color`,
@@ -4638,10 +5033,10 @@ function scrollbarStyle({ scrollbar, overflow }) {
4638
5033
  scrollbarStyle.__lookupStyles = ["scrollbar", "overflow"];
4639
5034
  //#endregion
4640
5035
  //#region src/styles/shadow.ts
4641
- function toBoxShadow(shadow) {
4642
- const { values, mods, colors } = parseStyle(shadow).groups[0] ?? makeEmptyDetails();
5036
+ function toBoxShadow(group) {
5037
+ const { values, mods, colors } = group;
4643
5038
  const mod = mods[0] || "";
4644
- const shadowColor = (colors && colors[0]) ?? "";
5039
+ const shadowColor = colors[0] ?? "";
4645
5040
  return [
4646
5041
  mod,
4647
5042
  ...values,
@@ -4652,7 +5047,8 @@ function shadowStyle({ shadow }) {
4652
5047
  if (!shadow) return null;
4653
5048
  if (shadow === true) shadow = "var(--shadow)";
4654
5049
  if (CSS_WIDE_KEYWORDS.has(shadow)) return { "box-shadow": shadow };
4655
- return { "box-shadow": shadow.split(",").map(toBoxShadow).join(",") };
5050
+ const { groups } = parseStyle(shadow);
5051
+ return { "box-shadow": groups.map(toBoxShadow).join(",") };
4656
5052
  }
4657
5053
  shadowStyle.__lookupStyles = ["shadow"];
4658
5054
  //#endregion
@@ -5264,6 +5660,8 @@ var SheetManager = class {
5264
5660
  propertyTypeResolver: new PropertyTypeResolver(),
5265
5661
  usageMap: /* @__PURE__ */ new Map(),
5266
5662
  touchCount: 0,
5663
+ touchTick: -1,
5664
+ touchedTick: /* @__PURE__ */ new Set(),
5267
5665
  serverClassSyncIndex: 0,
5268
5666
  rscStylesScanned: false,
5269
5667
  injectionMode: this.detectInjectionMode(root)
@@ -6174,6 +6572,16 @@ function formatCounterStyleRule(name, descriptors) {
6174
6572
  //#endregion
6175
6573
  //#region src/injector/injector.ts
6176
6574
  /**
6575
+ * Placeholder `KeyframesInfo` for a cache entry reserved by a batched write and
6576
+ * not yet backed by a real rule. Only ever read through `entry.pending`, which
6577
+ * is checked before the info is used.
6578
+ */
6579
+ const EMPTY_KEYFRAMES_INFO = {
6580
+ name: "",
6581
+ ruleIndex: -3,
6582
+ sheetIndex: -3
6583
+ };
6584
+ /**
6177
6585
  * Extract class names from `<style data-tasty-rsc>` tags.
6178
6586
  * The doubled-specificity pattern `.tXXX.tXXX` makes extraction reliable.
6179
6587
  */
@@ -6234,6 +6642,56 @@ var StyleInjector = class {
6234
6642
  get _sheetManager() {
6235
6643
  return this.sheetManager;
6236
6644
  }
6645
+ /**
6646
+ * Whether sheet writes should be queued instead of applied immediately.
6647
+ *
6648
+ * Only ever true on the client: SSR collects CSS as text and the RSC path
6649
+ * returns it as strings, so neither has a live sheet to batch writes against.
6650
+ *
6651
+ * In the default `true` mode a write is only queued inside an open batch
6652
+ * window — a commit in which `<TastyBatchProvider>` rendered and will
6653
+ * therefore flush in its insertion effect, before any layout effect can
6654
+ * measure. Everything else falls through to a synchronous write, so enabling
6655
+ * the flag cannot make a `useLayoutEffect` read an unstyled element.
6656
+ * `'always'` drops the gate and accepts that trade for wider coverage.
6657
+ */
6658
+ get batching() {
6659
+ const mode = this.config.batchInjection;
6660
+ if (!mode || typeof document === "undefined") return false;
6661
+ if (mode === "always") return true;
6662
+ if (isBatchWindowOpen()) return true;
6663
+ warnBatchProviderMissing();
6664
+ return false;
6665
+ }
6666
+ /**
6667
+ * Apply a sheet write now, or queue it when batching is on.
6668
+ * Returns the queue handle when deferred, so the caller can cancel it if it
6669
+ * disposes before the flush.
6670
+ */
6671
+ writeSheet(task) {
6672
+ if (this.batching) return enqueueStyleWrite(task);
6673
+ task();
6674
+ return null;
6675
+ }
6676
+ /**
6677
+ * Insert a global (non-class) rule, honouring batching.
6678
+ *
6679
+ * `onApplied` records the caller's dedupe bookkeeping. When the write is
6680
+ * queued it runs eagerly, so a repeat call before the flush bails out instead
6681
+ * of queueing the same rule twice — the same reasoning as
6682
+ * `insertPropertyRule`'s eager marking. When written synchronously it runs
6683
+ * only on success, preserving the existing retry-on-failure behaviour.
6684
+ */
6685
+ writeGlobalRule(registry, rules, key, root, onApplied) {
6686
+ if (this.batching) {
6687
+ onApplied?.();
6688
+ enqueueStyleWrite(() => {
6689
+ this.sheetManager.insertGlobalRule(registry, rules, key, root);
6690
+ });
6691
+ return;
6692
+ }
6693
+ if (this.sheetManager.insertGlobalRule(registry, rules, key, root)) onApplied?.();
6694
+ }
6237
6695
  constructor(config = {}) {
6238
6696
  if (config.namePrefix !== void 0) validateNamePrefix(config.namePrefix);
6239
6697
  this.config = config;
@@ -6311,12 +6769,18 @@ var StyleInjector = class {
6311
6769
  };
6312
6770
  const cacheKey = options?.cacheKey;
6313
6771
  let className;
6314
- let isPreAllocated = false;
6315
6772
  if (cacheKey && registry.cacheKeyToClassName.has(cacheKey)) {
6316
6773
  className = registry.cacheKeyToClassName.get(cacheKey);
6317
6774
  const existingRuleInfo = registry.rules.get(className);
6318
- isPreAllocated = existingRuleInfo.ruleIndex === -1 && existingRuleInfo.sheetIndex === -1;
6319
- if (!isPreAllocated) {
6775
+ if (existingRuleInfo.ruleIndex === -3) {
6776
+ registry.refCounts.set(className, (registry.refCounts.get(className) || 0) + 1);
6777
+ if (registry.metrics) registry.metrics.hits++;
6778
+ return {
6779
+ className,
6780
+ dispose: () => this.dispose(className, registry)
6781
+ };
6782
+ }
6783
+ if (!(existingRuleInfo.ruleIndex === -1 && existingRuleInfo.sheetIndex === -1)) {
6320
6784
  const currentRefCount = registry.refCounts.get(className) || 0;
6321
6785
  registry.refCounts.set(className, currentRefCount + 1);
6322
6786
  if (registry.metrics) registry.metrics.hits++;
@@ -6357,39 +6821,70 @@ var StyleInjector = class {
6357
6821
  rootPrefix: void 0
6358
6822
  };
6359
6823
  });
6360
- if (this.config.autoPropertyTypes !== false) {
6361
- const resolver = registry.propertyTypeResolver;
6362
- const defined = registry.injectedProperties;
6363
- for (const rule of rulesToInsert) {
6364
- if (!rule.declarations) continue;
6365
- resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
6366
- this.property(name, {
6367
- syntax,
6368
- inherits: true,
6369
- initialValue,
6370
- root
6824
+ const applySheetWrite = () => {
6825
+ if (this.config.autoPropertyTypes !== false) {
6826
+ const resolver = registry.propertyTypeResolver;
6827
+ const defined = registry.injectedProperties;
6828
+ for (const rule of rulesToInsert) {
6829
+ if (!rule.declarations) continue;
6830
+ resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
6831
+ this.property(name, {
6832
+ syntax,
6833
+ inherits: true,
6834
+ initialValue,
6835
+ root
6836
+ });
6371
6837
  });
6372
- });
6838
+ }
6373
6839
  }
6374
- }
6375
- const ruleInfo = this.sheetManager.insertRule(registry, rulesToInsert, className, root);
6376
- if (!ruleInfo) {
6377
- if (registry.metrics) registry.metrics.misses++;
6840
+ const ruleInfo = this.sheetManager.insertRule(registry, rulesToInsert, className, root);
6841
+ if (!ruleInfo) {
6842
+ if (registry.metrics) registry.metrics.misses++;
6843
+ return null;
6844
+ }
6845
+ registry.rules.set(className, ruleInfo);
6846
+ if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
6847
+ if (registry.metrics) {
6848
+ registry.metrics.totalInsertions++;
6849
+ registry.metrics.misses++;
6850
+ }
6851
+ return ruleInfo;
6852
+ };
6853
+ if (this.batching) {
6854
+ registry.rules.set(className, {
6855
+ className,
6856
+ ruleIndex: -3,
6857
+ sheetIndex: -3
6858
+ });
6859
+ if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
6860
+ registry.refCounts.set(className, 1);
6861
+ const queued = enqueueStyleWrite(() => {
6862
+ if (applySheetWrite()) return;
6863
+ registry.rules.set(className, {
6864
+ className,
6865
+ ruleIndex: -1,
6866
+ sheetIndex: -1
6867
+ });
6868
+ });
6378
6869
  return {
6379
6870
  className,
6380
- dispose: () => {}
6871
+ dispose: () => {
6872
+ if (!queued.done && registry.refCounts.get(className) === 1 && registry.rules.get(className)?.ruleIndex === -3) {
6873
+ queued.cancelled = true;
6874
+ registry.rules.delete(className);
6875
+ if (cacheKey) registry.cacheKeyToClassName.delete(cacheKey);
6876
+ registry.refCounts.set(className, 0);
6877
+ return;
6878
+ }
6879
+ this.dispose(className, registry);
6880
+ }
6381
6881
  };
6382
6882
  }
6883
+ if (!applySheetWrite()) return {
6884
+ className,
6885
+ dispose: () => {}
6886
+ };
6383
6887
  registry.refCounts.set(className, 1);
6384
- if (isPreAllocated) registry.rules.set(className, ruleInfo);
6385
- else {
6386
- registry.rules.set(className, ruleInfo);
6387
- if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
6388
- }
6389
- if (registry.metrics) {
6390
- registry.metrics.totalInsertions++;
6391
- registry.metrics.misses++;
6392
- }
6393
6888
  return {
6394
6889
  className,
6395
6890
  dispose: () => this.dispose(className, registry)
@@ -6404,24 +6899,41 @@ var StyleInjector = class {
6404
6899
  const root = options?.root || document;
6405
6900
  const registry = this.sheetManager.getRegistry(root);
6406
6901
  if (!rules || rules.length === 0) return { dispose: () => {} };
6407
- if (this.config.autoPropertyTypes !== false) {
6408
- const resolver = registry.propertyTypeResolver;
6409
- const defined = registry.injectedProperties;
6410
- for (const rule of rules) {
6411
- if (!rule.declarations) continue;
6412
- resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
6413
- this.property(name, {
6414
- syntax,
6415
- inherits: true,
6416
- initialValue,
6417
- root
6902
+ const key = `global:${this.globalRuleCounter++}`;
6903
+ const applyWrite = () => {
6904
+ if (this.config.autoPropertyTypes !== false) {
6905
+ const resolver = registry.propertyTypeResolver;
6906
+ const defined = registry.injectedProperties;
6907
+ for (const rule of rules) {
6908
+ if (!rule.declarations) continue;
6909
+ resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
6910
+ this.property(name, {
6911
+ syntax,
6912
+ inherits: true,
6913
+ initialValue,
6914
+ root
6915
+ });
6418
6916
  });
6419
- });
6917
+ }
6420
6918
  }
6919
+ const inserted = this.sheetManager.insertGlobalRule(registry, rules, key, root);
6920
+ if (registry.metrics) registry.metrics.totalInsertions++;
6921
+ return inserted;
6922
+ };
6923
+ if (this.batching) {
6924
+ let info = null;
6925
+ const queued = enqueueStyleWrite(() => {
6926
+ info = applyWrite();
6927
+ });
6928
+ return { dispose: () => {
6929
+ if (!queued.done) {
6930
+ queued.cancelled = true;
6931
+ return;
6932
+ }
6933
+ if (info) this.sheetManager.deleteGlobalRule(registry, key);
6934
+ } };
6421
6935
  }
6422
- const key = `global:${this.globalRuleCounter++}`;
6423
- const info = this.sheetManager.insertGlobalRule(registry, rules, key, root);
6424
- if (registry.metrics) registry.metrics.totalInsertions++;
6936
+ const info = applyWrite();
6425
6937
  return { dispose: () => {
6426
6938
  if (info) this.sheetManager.deleteGlobalRule(registry, key);
6427
6939
  } };
@@ -6433,12 +6945,24 @@ var StyleInjector = class {
6433
6945
  */
6434
6946
  injectRawCSS(css, options) {
6435
6947
  const root = options?.root || document;
6436
- return this.sheetManager.injectRawCSS(css, root);
6948
+ if (!this.batching) return this.sheetManager.injectRawCSS(css, root);
6949
+ let result = null;
6950
+ const queued = enqueueStyleWrite(() => {
6951
+ result = this.sheetManager.injectRawCSS(css, root);
6952
+ });
6953
+ return { dispose: () => {
6954
+ if (!queued.done) {
6955
+ queued.cancelled = true;
6956
+ return;
6957
+ }
6958
+ result?.dispose();
6959
+ } };
6437
6960
  }
6438
6961
  /**
6439
6962
  * Get raw CSS text for SSR
6440
6963
  */
6441
6964
  getRawCSSText(options) {
6965
+ flushStyles();
6442
6966
  const root = options?.root || document;
6443
6967
  return this.sheetManager.getRawCSSText(root);
6444
6968
  }
@@ -6475,6 +6999,7 @@ var StyleInjector = class {
6475
6999
  * Force bulk cleanup of unused styles
6476
7000
  */
6477
7001
  cleanup(root) {
7002
+ flushStyles();
6478
7003
  const registry = this.sheetManager.getRegistry(root || document);
6479
7004
  this.sheetManager.forceCleanup(registry);
6480
7005
  }
@@ -6482,6 +7007,7 @@ var StyleInjector = class {
6482
7007
  * Get CSS text from all sheets (for SSR)
6483
7008
  */
6484
7009
  getCSSText(options) {
7010
+ flushStyles();
6485
7011
  const root = options?.root || document;
6486
7012
  const registry = this.sheetManager.getRegistry(root);
6487
7013
  return this.sheetManager.getCSSText(registry);
@@ -6490,6 +7016,7 @@ var StyleInjector = class {
6490
7016
  * Get CSS only for the provided tasty classNames (e.g., ["t0","t3"])
6491
7017
  */
6492
7018
  getCSSTextForClasses(classNames, options) {
7019
+ flushStyles();
6493
7020
  const root = options?.root || document;
6494
7021
  const registry = this.sheetManager.getRegistry(root);
6495
7022
  const cssChunks = [];
@@ -6514,6 +7041,7 @@ var StyleInjector = class {
6514
7041
  * Get cache performance metrics
6515
7042
  */
6516
7043
  getMetrics(options) {
7044
+ flushStyles();
6517
7045
  const root = options?.root || document;
6518
7046
  const registry = this.sheetManager.getRegistry(root);
6519
7047
  return this.sheetManager.getMetrics(registry);
@@ -6593,7 +7121,7 @@ var StyleInjector = class {
6593
7121
  declarations
6594
7122
  };
6595
7123
  registry.injectedProperties.set(cssName, normalizePropertyDefinition(definition));
6596
- this.sheetManager.insertGlobalRule(registry, [rule], `property:${cacheKey}`, root);
7124
+ this.writeGlobalRule(registry, [rule], `property:${cacheKey}`, root);
6597
7125
  }
6598
7126
  /**
6599
7127
  * Check whether a given @property name was already injected by this injector.
@@ -6604,6 +7132,7 @@ var StyleInjector = class {
6604
7132
  * - `--name` → checks `--name` (legacy format)
6605
7133
  */
6606
7134
  isPropertyDefined(name, options) {
7135
+ flushStyles();
6607
7136
  const root = options?.root || document;
6608
7137
  const registry = this.sheetManager.getRegistry(root);
6609
7138
  const effectiveResult = getEffectiveDefinition(name, {});
@@ -6625,7 +7154,9 @@ var StyleInjector = class {
6625
7154
  selector: "@font-face",
6626
7155
  declarations: formatFontFaceDeclarations(family, descriptors)
6627
7156
  };
6628
- if (this.sheetManager.insertGlobalRule(registry, [rule], `fontface:${hash}`, root)) registry.injectedFontFaces.add(hash);
7157
+ this.writeGlobalRule(registry, [rule], `fontface:${hash}`, root, () => {
7158
+ registry.injectedFontFaces.add(hash);
7159
+ });
6629
7160
  }
6630
7161
  /**
6631
7162
  * Inject a CSS @counter-style rule.
@@ -6643,13 +7174,17 @@ var StyleInjector = class {
6643
7174
  const existingIsStrong = registry.injectedCounterStyles.get(name);
6644
7175
  if (existingIsStrong !== void 0) {
6645
7176
  if (isWeak || existingIsStrong === true) return;
6646
- this.sheetManager.deleteGlobalRule(registry, `counterstyle:${name}`);
7177
+ this.writeSheet(() => {
7178
+ this.sheetManager.deleteGlobalRule(registry, `counterstyle:${name}`);
7179
+ });
6647
7180
  }
6648
7181
  const rule = {
6649
7182
  selector: `@counter-style ${name}`,
6650
7183
  declarations: formatCounterStyleDeclarations(descriptors)
6651
7184
  };
6652
- if (this.sheetManager.insertGlobalRule(registry, [rule], `counterstyle:${name}`, root)) registry.injectedCounterStyles.set(name, !isWeak);
7185
+ this.writeGlobalRule(registry, [rule], `counterstyle:${name}`, root, () => {
7186
+ registry.injectedCounterStyles.set(name, !isWeak);
7187
+ });
6653
7188
  }
6654
7189
  /**
6655
7190
  * Inject a CSS @function rule (custom function).
@@ -6668,13 +7203,17 @@ var StyleInjector = class {
6668
7203
  const existingIsStrong = registry.injectedFunctions.get(cssName);
6669
7204
  if (existingIsStrong !== void 0) {
6670
7205
  if (isWeak || existingIsStrong === true) return;
6671
- this.sheetManager.deleteGlobalRule(registry, `function:${cssName}`);
7206
+ this.writeSheet(() => {
7207
+ this.sheetManager.deleteGlobalRule(registry, `function:${cssName}`);
7208
+ });
6672
7209
  }
6673
7210
  const rule = {
6674
7211
  selector: formatFunctionPrelude(name, definition.args, definition.returns),
6675
7212
  declarations: formatFunctionDeclarations(definition)
6676
7213
  };
6677
- if (this.sheetManager.insertGlobalRule(registry, [rule], `function:${cssName}`, root)) registry.injectedFunctions.set(cssName, !isWeak);
7214
+ this.writeGlobalRule(registry, [rule], `function:${cssName}`, root, () => {
7215
+ registry.injectedFunctions.set(cssName, !isWeak);
7216
+ });
6678
7217
  }
6679
7218
  /**
6680
7219
  * Inject keyframes and return object with toString() and dispose()
@@ -6712,29 +7251,53 @@ var StyleInjector = class {
6712
7251
  registry.keyframesNameToContent.set(providedName, contentHash);
6713
7252
  }
6714
7253
  } else actualName = makeKeyframeName(this.namePrefix, String(registry.keyframesCounter++));
6715
- const result = this.sheetManager.insertKeyframes(registry, steps, actualName, root);
6716
- if (!result) return {
6717
- toString: () => "",
6718
- dispose: () => {}
7254
+ const applyWrite = () => {
7255
+ const result = this.sheetManager.insertKeyframes(registry, steps, actualName, root);
7256
+ if (!result) return false;
7257
+ const { info, declarations } = result;
7258
+ if (this.config.autoPropertyTypes !== false && declarations) registry.propertyTypeResolver.scanDeclarations(declarations, (name) => registry.injectedProperties.has(name), (name, syntax, initialValue) => {
7259
+ this.property(name, {
7260
+ syntax,
7261
+ inherits: true,
7262
+ initialValue,
7263
+ root
7264
+ });
7265
+ });
7266
+ const entry = registry.keyframesCache.get(contentHash);
7267
+ if (entry) {
7268
+ entry.info = info;
7269
+ entry.pending = void 0;
7270
+ } else registry.keyframesCache.set(contentHash, {
7271
+ name: actualName,
7272
+ refCount: 1,
7273
+ info
7274
+ });
7275
+ if (registry.metrics) {
7276
+ registry.metrics.totalInsertions++;
7277
+ registry.metrics.misses++;
7278
+ }
7279
+ return true;
6719
7280
  };
6720
- const { info, declarations } = result;
6721
- if (this.config.autoPropertyTypes !== false && declarations) registry.propertyTypeResolver.scanDeclarations(declarations, (name) => registry.injectedProperties.has(name), (name, syntax, initialValue) => {
6722
- this.property(name, {
6723
- syntax,
6724
- inherits: true,
6725
- initialValue,
6726
- root
7281
+ if (this.batching) {
7282
+ const entry = {
7283
+ name: actualName,
7284
+ refCount: 1,
7285
+ info: EMPTY_KEYFRAMES_INFO
7286
+ };
7287
+ registry.keyframesCache.set(contentHash, entry);
7288
+ entry.pending = enqueueStyleWrite(() => {
7289
+ if (applyWrite()) return;
7290
+ if (registry.keyframesCache.get(contentHash) === entry) registry.keyframesCache.delete(contentHash);
6727
7291
  });
6728
- });
6729
- registry.keyframesCache.set(contentHash, {
6730
- name: actualName,
6731
- refCount: 1,
6732
- info
6733
- });
6734
- if (registry.metrics) {
6735
- registry.metrics.totalInsertions++;
6736
- registry.metrics.misses++;
7292
+ return {
7293
+ toString: () => actualName,
7294
+ dispose: () => this.disposeKeyframes(contentHash, registry)
7295
+ };
6737
7296
  }
7297
+ if (!applyWrite()) return {
7298
+ toString: () => "",
7299
+ dispose: () => {}
7300
+ };
6738
7301
  return {
6739
7302
  toString: () => actualName,
6740
7303
  dispose: () => this.disposeKeyframes(contentHash, registry)
@@ -6748,7 +7311,8 @@ var StyleInjector = class {
6748
7311
  if (!entry) return;
6749
7312
  entry.refCount--;
6750
7313
  if (entry.refCount <= 0) {
6751
- this.sheetManager.deleteKeyframes(registry, entry.info);
7314
+ if (entry.pending && !entry.pending.done) entry.pending.cancelled = true;
7315
+ else this.sheetManager.deleteKeyframes(registry, entry.info);
6752
7316
  registry.keyframesCache.delete(contentHash);
6753
7317
  for (const [name, hash] of registry.keyframesNameToContent.entries()) if (hash === contentHash) {
6754
7318
  registry.keyframesNameToContent.delete(name);
@@ -6773,6 +7337,11 @@ var StyleInjector = class {
6773
7337
  const root = options?.root || document;
6774
7338
  const registry = this.sheetManager.getRegistry(root);
6775
7339
  const now = Date.now();
7340
+ if (registry.touchTick !== now) {
7341
+ registry.touchTick = now;
7342
+ registry.touchedTick.clear();
7343
+ } else if (registry.touchedTick.has(className)) return;
7344
+ registry.touchedTick.add(className);
6776
7345
  const parts = className.indexOf(" ") === -1 ? [className] : className.split(" ");
6777
7346
  for (const cls of parts) {
6778
7347
  if (!this.classRegex.test(cls)) continue;
@@ -6813,6 +7382,7 @@ var StyleInjector = class {
6813
7382
  * @returns Number of styles evicted.
6814
7383
  */
6815
7384
  gc(options) {
7385
+ flushStyles();
6816
7386
  if (typeof document === "undefined") return 0;
6817
7387
  if (this.pendingGCHandle != null) {
6818
7388
  if (typeof cancelIdleCallback !== "undefined") cancelIdleCallback(this.pendingGCHandle);
@@ -6862,6 +7432,7 @@ var StyleInjector = class {
6862
7432
  * Destroy all resources for a root
6863
7433
  */
6864
7434
  destroy(root) {
7435
+ flushStyles();
6865
7436
  const targetRoot = root || document;
6866
7437
  this.sheetManager.cleanup(targetRoot);
6867
7438
  if (this.pendingGCHandle != null && !this.sheetManager.hasActiveRoots()) {
@@ -12310,6 +12881,6 @@ function resetConfig() {
12310
12881
  delete storage[GLOBAL_INJECTOR_KEY];
12311
12882
  }
12312
12883
  //#endregion
12313
- export { SheetManager as $, getNamedColorHex as $t, FLOW_STYLES as A, registerFunctionPolyfill as At, createStateParserContext as B, stringifyStyles as Bt, BASE_STYLES as C, LAYOUT_CHUNK_STYLES as Ct, COLOR_STYLES as D, formatFunctionRule as Dt, BLOCK_STYLES as E, extractLocalFunctions as Et, hasPipelineCacheEntry as F, getGlobalParser as Ft, StyleInjector as G, createColorFunc as Gt, extractPredefinedStateRefs as H, okhstPlugin as Ht, isSelector as I, getGlobalPredefinedTokens as It, hasLocalCounterStyle as J, colorInitialValueToComponents as Jt, extractLocalCounterStyle as K, isDevEnv as Kt, renderStyles as L, normalizeColorTokenValue as Lt, OUTER_STYLES as M, CUSTOM_UNITS as Mt, POSITION_STYLES as N, DIRECTIONS as Nt, CONTAINER_STYLES as O, hasLocalFunctions as Ot, TEXT_STYLES as P, filterMods as Pt, hasLocalFontFace as Q, getComponentPropertySyntax as Qt, parseStateKey as R, parseColor as Rt, baseStylePropsRegistry as S, FONT_CHUNK_STYLES as St, BLOCK_OUTER_STYLES as T, STYLE_TO_CHUNK as Tt, getGlobalPredefinedStates as U, okhslFunction as Ut, extractLocalPredefinedStates as V, okhstFunction as Vt, setGlobalPredefinedStates as W, okhslPlugin as Wt, fontFaceContentHash as X, getColorSpaceFunc as Xt, extractLocalFontFace as Y, getColorSpaceComponents as Yt, formatFontFaceRule as Z, getColorSpaceSuffix as Zt, isFunctionsPolyfillEnabled as _, propHandlerRegistry as _t, getGlobalCounterStyles as a, Lru as an, DEFAULT_NAME_PREFIX as at, resetConfig as b, DIMENSION_CHUNK_STYLES as bt, getGlobalInjector as c, makeCounterStyleName as ct, getGlobalStyles as d, validateNamePrefix as dt, getRgbValuesFromRgbaString as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, hashString as ft, isConfigLocked as g, parsePropertyToken as gt, hasStylesGenerated as h, hasLocalProperties as ht, getGlobalConfigTokens as i, resolveFunctionColor as in, PropertyTypeResolver as it, INNER_STYLES as j, registerLocalFunctionPolyfills as jt, DIMENSION_STYLES as k, parseFunctionName as kt, getGlobalKeyframes as l, makeKeyframeName as lt, hasGlobalRecipes as m, getEffectiveDefinition as mt, getConfig as n, hslToRgbValues as nn, styleHandlers as nt, getGlobalFontFaces as o, normalizeDslName as on, DEFAULT_ZERO_NAME_PREFIX as ot, hasGlobalKeyframes as p, extractLocalProperties as pt, formatCounterStyleRule as q, StyleParser as qt, getEffectiveProperties as r, strToRgb as rn, createStyle as rt, getGlobalFunctions as s, makeClassName as st, configure as t, hexToRgb as tn, defineHandler as tt, getGlobalRecipes as u, tastyClassRegex as ut, isTestEnvironment as v, APPEARANCE_CHUNK_STYLES as vt, BLOCK_INNER_STYLES as w, POSITION_CHUNK_STYLES as wt, generateTypographyTokens as x, DISPLAY_CHUNK_STYLES as xt, markStylesGenerated as y, CHUNK_NAMES as yt, camelToKebab as z, parseStyle as zt };
12884
+ export { SheetManager as $, colorInitialValueToComponents as $t, FLOW_STYLES as A, STYLE_TO_CHUNK as At, createStateParserContext as B, getGlobalParser as Bt, BASE_STYLES as C, APPEARANCE_CHUNK_STYLES as Ct, COLOR_STYLES as D, FONT_CHUNK_STYLES as Dt, BLOCK_STYLES as E, DISPLAY_CHUNK_STYLES as Et, hasPipelineCacheEntry as F, registerFunctionPolyfill as Ft, StyleInjector as G, stringifyStyles as Gt, extractPredefinedStateRefs as H, normalizeColorTokenValue as Ht, isSelector as I, registerLocalFunctionPolyfills as It, hasLocalCounterStyle as J, okhslFunction as Jt, extractLocalCounterStyle as K, okhstFunction as Kt, renderStyles as L, CUSTOM_UNITS as Lt, OUTER_STYLES as M, formatFunctionRule as Mt, POSITION_STYLES as N, hasLocalFunctions as Nt, CONTAINER_STYLES as O, LAYOUT_CHUNK_STYLES as Ot, TEXT_STYLES as P, parseFunctionName as Pt, hasLocalFontFace as Q, StyleParser as Qt, parseStateKey as R, DIRECTIONS as Rt, baseStylePropsRegistry as S, propHandlerRegistry as St, BLOCK_OUTER_STYLES as T, DIMENSION_CHUNK_STYLES as Tt, getGlobalPredefinedStates as U, parseColor as Ut, extractLocalPredefinedStates as V, getGlobalPredefinedTokens as Vt, setGlobalPredefinedStates as W, parseStyle as Wt, fontFaceContentHash as X, createColorFunc as Xt, extractLocalFontFace as Y, okhslPlugin as Yt, formatFontFaceRule as Z, isDevEnv as Zt, isFunctionsPolyfillEnabled as _, hashString as _t, getGlobalCounterStyles as a, resolveFunctionColor as an, closeBatchWindow as at, resetConfig as b, hasLocalProperties as bt, getGlobalInjector as c, getRgbValuesFromRgbaString as cn, openBatchWindow as ct, getGlobalStyles as d, strToRgb as dn, DEFAULT_ZERO_NAME_PREFIX as dt, convertColorChainToComponentChain as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, normalizeDslName as fn, makeClassName as ft, isConfigLocked as g, validateNamePrefix as gt, hasStylesGenerated as h, tastyClassRegex as ht, getGlobalConfigTokens as i, overrideColorAlpha as in, PropertyTypeResolver as it, INNER_STYLES as j, extractLocalFunctions as jt, DIMENSION_STYLES as k, POSITION_CHUNK_STYLES as kt, getGlobalKeyframes as l, hexToRgb as ln, resetStyleBatch as lt, hasGlobalRecipes as m, makeKeyframeName as mt, getConfig as n, getColorSpaceSuffix as nn, styleHandlers as nt, getGlobalFontFaces as o, Lru as on, flushStyles as ot, hasGlobalKeyframes as p, makeCounterStyleName as pt, formatCounterStyleRule as q, okhstPlugin as qt, getEffectiveProperties as r, getComponentPropertySyntax as rn, createStyle as rt, getGlobalFunctions as s, getNamedColorHex as sn, hasPendingStyleWrites as st, configure as t, getColorSpaceComponents as tn, defineHandler as tt, getGlobalRecipes as u, hslToRgbValues as un, DEFAULT_NAME_PREFIX as ut, isTestEnvironment as v, extractLocalProperties as vt, BLOCK_INNER_STYLES as w, CHUNK_NAMES as wt, generateTypographyTokens as x, parsePropertyToken as xt, markStylesGenerated as y, getEffectiveDefinition as yt, camelToKebab as z, filterMods as zt };
12314
12885
 
12315
- //# sourceMappingURL=config-DOCuTykY.js.map
12886
+ //# sourceMappingURL=config-BrDt11hO.js.map