@tenphi/tasty 3.1.0 → 3.3.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 +1 -0
- package/dist/{babel-R2qT7yLN.d.ts → babel-BUQGeOXA.d.ts} +2 -2
- package/dist/{collector-D7yzmG5G.js → collector-BNQJyxFh.js} +3 -3
- package/dist/{collector-D7yzmG5G.js.map → collector-BNQJyxFh.js.map} +1 -1
- package/dist/{collector-D8o4Wdq-.d.ts → collector-DUaHCcTS.d.ts} +2 -2
- package/dist/{config-De8L9NWM.d.ts → config-YsxGv4tq.d.ts} +101 -10
- package/dist/{config-CwQ-fAsp.js → config-zkrtb424.js} +739 -960
- package/dist/config-zkrtb424.js.map +1 -0
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +6 -6
- package/dist/{core-CqSh853z.js → core-Bf48Z1I-.js} +57 -41
- package/dist/core-Bf48Z1I-.js.map +1 -0
- package/dist/{css-writer-dS1srTkS.js → css-writer-B-SvwNbq.js} +3 -3
- package/dist/{css-writer-dS1srTkS.js.map → css-writer-B-SvwNbq.js.map} +1 -1
- package/dist/{format-rules-CaW4lJGg.js → format-rules-Co21uwA3.js} +5 -20
- package/dist/format-rules-Co21uwA3.js.map +1 -0
- package/dist/{hydrate-uFv9kx7G.js → hydrate-D38TyhAG.js} +2 -2
- package/dist/{hydrate-uFv9kx7G.js.map → hydrate-D38TyhAG.js.map} +1 -1
- package/dist/{index-KUYya3x7.d.ts → index-Cd45t5NM.d.ts} +108 -3
- package/dist/{index-DzGxoeyN.d.ts → index-DhhUI0yi.d.ts} +63 -12
- package/dist/index.d.ts +5 -5
- package/dist/index.js +62 -8
- package/dist/index.js.map +1 -1
- package/dist/{keyframes-DaZhjqkd.js → keyframes-mDAB6pLf.js} +2 -2
- package/dist/{keyframes-DaZhjqkd.js.map → keyframes-mDAB6pLf.js.map} +1 -1
- package/dist/{merge-styles-H8LFJ5HY.js → merge-styles-CFwtEHzv.js} +2 -2
- package/dist/{merge-styles-H8LFJ5HY.js.map → merge-styles-CFwtEHzv.js.map} +1 -1
- package/dist/{merge-styles-DLm4wdnb.d.ts → merge-styles-CU7JbEwg.d.ts} +2 -2
- package/dist/{resolve-recipes-aN94fjqS.js → resolve-recipes-B8pIVinB.js} +3 -3
- package/dist/{resolve-recipes-aN94fjqS.js.map → resolve-recipes-B8pIVinB.js.map} +1 -1
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro.js +3 -3
- package/dist/ssr/index.d.ts +1 -1
- package/dist/ssr/index.js +3 -3
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +4 -4
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +4 -4
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/docs/configuration.md +195 -15
- package/docs/dsl.md +28 -6
- package/docs/injector.md +11 -0
- package/docs/methodology.md +1 -1
- package/docs/pipeline.md +1 -4
- package/docs/react-api.md +1 -1
- package/docs/styles.md +6 -1
- package/package.json +7 -6
- package/dist/config-CwQ-fAsp.js.map +0 -1
- package/dist/core-CqSh853z.js.map +0 -1
- package/dist/format-rules-CaW4lJGg.js.map +0 -1
|
@@ -52,16 +52,6 @@ const COLOR_FUNCS = new Set([
|
|
|
52
52
|
]);
|
|
53
53
|
/** A value that is a single `name(args)` call: captures the name and the args. */
|
|
54
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
|
-
}
|
|
65
55
|
const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
|
|
66
56
|
const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
|
|
67
57
|
const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
|
|
@@ -197,9 +187,6 @@ const OKLab_to_linear_sRGB_coefficients = [
|
|
|
197
187
|
]]
|
|
198
188
|
];
|
|
199
189
|
const dotXY = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
200
|
-
function srgbToLinear(c) {
|
|
201
|
-
return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
202
|
-
}
|
|
203
190
|
const INV_GAMMA = 1 / 2.4;
|
|
204
191
|
function srgbLinearToGamma(val) {
|
|
205
192
|
const sign = val < 0 ? -1 : 1;
|
|
@@ -241,22 +228,6 @@ const oklabToLinearSrgb = (lab) => {
|
|
|
241
228
|
-.004196076138675526 * l - .703418617935936 * m + 1.7076146940746113 * s
|
|
242
229
|
];
|
|
243
230
|
};
|
|
244
|
-
const linearSrgbToOklab = (rgb) => {
|
|
245
|
-
const r = rgb[0];
|
|
246
|
-
const g = rgb[1];
|
|
247
|
-
const b = rgb[2];
|
|
248
|
-
const l = .4122214708 * r + .5363325363 * g + .0514459929 * b;
|
|
249
|
-
const m = .2119034982 * r + .6806995451 * g + .1073969566 * b;
|
|
250
|
-
const s = .0883024619 * r + .2817188376 * g + .6299787005 * b;
|
|
251
|
-
const l_ = Math.cbrt(l);
|
|
252
|
-
const m_ = Math.cbrt(m);
|
|
253
|
-
const s_ = Math.cbrt(s);
|
|
254
|
-
return [
|
|
255
|
-
.2104542553 * l_ + .793617785 * m_ - .0040720468 * s_,
|
|
256
|
-
1.9779984951 * l_ - 2.428592205 * m_ + .4505937099 * s_,
|
|
257
|
-
.0259040371 * l_ + .7827717662 * m_ - .808675766 * s_
|
|
258
|
-
];
|
|
259
|
-
};
|
|
260
231
|
const computeMaxSaturationOKLC = (a, b) => {
|
|
261
232
|
const okCoeff = OKLab_to_linear_sRGB_coefficients;
|
|
262
233
|
const lmsToRgb = LMS_to_linear_sRGB_M;
|
|
@@ -450,53 +421,6 @@ function hslToRgbValues(h, s, l) {
|
|
|
450
421
|
];
|
|
451
422
|
}
|
|
452
423
|
/**
|
|
453
|
-
* RGB (0-255) to HSL.
|
|
454
|
-
* @returns [h (0-360), s (0-1), l (0-1)]
|
|
455
|
-
*/
|
|
456
|
-
function rgbToHsl(r, g, b) {
|
|
457
|
-
r /= 255;
|
|
458
|
-
g /= 255;
|
|
459
|
-
b /= 255;
|
|
460
|
-
const max = Math.max(r, g, b);
|
|
461
|
-
const min = Math.min(r, g, b);
|
|
462
|
-
const l = (max + min) / 2;
|
|
463
|
-
if (max === min) return [
|
|
464
|
-
0,
|
|
465
|
-
0,
|
|
466
|
-
l
|
|
467
|
-
];
|
|
468
|
-
const d = max - min;
|
|
469
|
-
const s = l > .5 ? d / (2 - max - min) : d / (max + min);
|
|
470
|
-
let h;
|
|
471
|
-
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
472
|
-
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
473
|
-
else h = ((r - g) / d + 4) / 6;
|
|
474
|
-
return [
|
|
475
|
-
h * 360,
|
|
476
|
-
s,
|
|
477
|
-
l
|
|
478
|
-
];
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* RGB (0-255) to OKLCH via sRGB -> linear sRGB -> OKLab -> OKLCH.
|
|
482
|
-
* @returns [L, C, H] where H is in degrees (0-360)
|
|
483
|
-
*/
|
|
484
|
-
function rgbToOklch(r, g, b) {
|
|
485
|
-
const [L, a, bLab] = linearSrgbToOklab([
|
|
486
|
-
srgbToLinear(r / 255),
|
|
487
|
-
srgbToLinear(g / 255),
|
|
488
|
-
srgbToLinear(b / 255)
|
|
489
|
-
]);
|
|
490
|
-
const C = Math.sqrt(a * a + bLab * bLab);
|
|
491
|
-
let H = Math.atan2(bLab, a) * 180 / Math.PI;
|
|
492
|
-
if (H < 0) H += 360;
|
|
493
|
-
return [
|
|
494
|
-
L,
|
|
495
|
-
C,
|
|
496
|
-
H
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
424
|
* OKHSL to sRGB (0-1 range).
|
|
501
425
|
* @param h - Hue in degrees (0-360)
|
|
502
426
|
* @param s - Saturation (0-1)
|
|
@@ -700,80 +624,6 @@ function getNamedColorHex() {
|
|
|
700
624
|
]);
|
|
701
625
|
return _namedColorHex;
|
|
702
626
|
}
|
|
703
|
-
const hexCharToNum = (c) => {
|
|
704
|
-
if (c >= 48 && c <= 57) return c - 48;
|
|
705
|
-
if (c >= 65 && c <= 70) return c - 55;
|
|
706
|
-
if (c >= 97 && c <= 102) return c - 87;
|
|
707
|
-
return -1;
|
|
708
|
-
};
|
|
709
|
-
/**
|
|
710
|
-
* Parse a hex color string to RGBA values (RGB 0-255, alpha 0-1).
|
|
711
|
-
* Supports 3, 4, 6, and 8 character hex values (with or without `#`).
|
|
712
|
-
* For 3/6-char hex (no alpha channel), alpha defaults to 1.
|
|
713
|
-
*/
|
|
714
|
-
function hexToRgbaValues(hex) {
|
|
715
|
-
let start = 0;
|
|
716
|
-
if (hex.charCodeAt(0) === 35) start = 1;
|
|
717
|
-
const len = hex.length - start;
|
|
718
|
-
if (len === 3) {
|
|
719
|
-
const r = hexCharToNum(hex.charCodeAt(start));
|
|
720
|
-
const g = hexCharToNum(hex.charCodeAt(start + 1));
|
|
721
|
-
const b = hexCharToNum(hex.charCodeAt(start + 2));
|
|
722
|
-
if (r < 0 || g < 0 || b < 0) return null;
|
|
723
|
-
return [
|
|
724
|
-
r * 17,
|
|
725
|
-
g * 17,
|
|
726
|
-
b * 17,
|
|
727
|
-
1
|
|
728
|
-
];
|
|
729
|
-
}
|
|
730
|
-
if (len === 4) {
|
|
731
|
-
const r = hexCharToNum(hex.charCodeAt(start));
|
|
732
|
-
const g = hexCharToNum(hex.charCodeAt(start + 1));
|
|
733
|
-
const b = hexCharToNum(hex.charCodeAt(start + 2));
|
|
734
|
-
const a = hexCharToNum(hex.charCodeAt(start + 3));
|
|
735
|
-
if (r < 0 || g < 0 || b < 0 || a < 0) return null;
|
|
736
|
-
return [
|
|
737
|
-
r * 17,
|
|
738
|
-
g * 17,
|
|
739
|
-
b * 17,
|
|
740
|
-
a * 17 / 255
|
|
741
|
-
];
|
|
742
|
-
}
|
|
743
|
-
if (len === 6) {
|
|
744
|
-
const r1 = hexCharToNum(hex.charCodeAt(start));
|
|
745
|
-
const r2 = hexCharToNum(hex.charCodeAt(start + 1));
|
|
746
|
-
const g1 = hexCharToNum(hex.charCodeAt(start + 2));
|
|
747
|
-
const g2 = hexCharToNum(hex.charCodeAt(start + 3));
|
|
748
|
-
const b1 = hexCharToNum(hex.charCodeAt(start + 4));
|
|
749
|
-
const b2 = hexCharToNum(hex.charCodeAt(start + 5));
|
|
750
|
-
if (r1 < 0 || r2 < 0 || g1 < 0 || g2 < 0 || b1 < 0 || b2 < 0) return null;
|
|
751
|
-
return [
|
|
752
|
-
r1 * 16 + r2,
|
|
753
|
-
g1 * 16 + g2,
|
|
754
|
-
b1 * 16 + b2,
|
|
755
|
-
1
|
|
756
|
-
];
|
|
757
|
-
}
|
|
758
|
-
if (len === 8) {
|
|
759
|
-
const r1 = hexCharToNum(hex.charCodeAt(start));
|
|
760
|
-
const r2 = hexCharToNum(hex.charCodeAt(start + 1));
|
|
761
|
-
const g1 = hexCharToNum(hex.charCodeAt(start + 2));
|
|
762
|
-
const g2 = hexCharToNum(hex.charCodeAt(start + 3));
|
|
763
|
-
const b1 = hexCharToNum(hex.charCodeAt(start + 4));
|
|
764
|
-
const b2 = hexCharToNum(hex.charCodeAt(start + 5));
|
|
765
|
-
const a1 = hexCharToNum(hex.charCodeAt(start + 6));
|
|
766
|
-
const a2 = hexCharToNum(hex.charCodeAt(start + 7));
|
|
767
|
-
if (r1 < 0 || r2 < 0 || g1 < 0 || g2 < 0 || b1 < 0 || b2 < 0 || a1 < 0 || a2 < 0) return null;
|
|
768
|
-
return [
|
|
769
|
-
r1 * 16 + r2,
|
|
770
|
-
g1 * 16 + g2,
|
|
771
|
-
b1 * 16 + b2,
|
|
772
|
-
(a1 * 16 + a2) / 255
|
|
773
|
-
];
|
|
774
|
-
}
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
627
|
/**
|
|
778
628
|
* Convert hex color string to `rgb()` CSS string.
|
|
779
629
|
* Supports 3, 4, 6, and 8 character hex values (with or without `#`).
|
|
@@ -877,415 +727,7 @@ function oklchStringToRgb(oklchStr) {
|
|
|
877
727
|
return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
|
|
878
728
|
}
|
|
879
729
|
//#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
|
|
1015
730
|
//#region src/utils/color-space.ts
|
|
1016
|
-
let currentColorSpace = "oklch";
|
|
1017
|
-
const colorSpaceCache = new Lru(500);
|
|
1018
|
-
const componentsCache = new Lru(500);
|
|
1019
|
-
function clearColorCaches() {
|
|
1020
|
-
colorSpaceCache.clear();
|
|
1021
|
-
componentsCache.clear();
|
|
1022
|
-
}
|
|
1023
|
-
function setColorSpace(space) {
|
|
1024
|
-
currentColorSpace = space;
|
|
1025
|
-
clearColorCaches();
|
|
1026
|
-
}
|
|
1027
|
-
function resetColorSpace() {
|
|
1028
|
-
currentColorSpace = "oklch";
|
|
1029
|
-
clearColorCaches();
|
|
1030
|
-
}
|
|
1031
|
-
function getColorSpaceSuffix() {
|
|
1032
|
-
return currentColorSpace;
|
|
1033
|
-
}
|
|
1034
|
-
function formatNum(n, precision) {
|
|
1035
|
-
return parseFloat(n.toFixed(precision)).toString();
|
|
1036
|
-
}
|
|
1037
|
-
function formatRgbComponent(n) {
|
|
1038
|
-
return parseFloat(n.toFixed(1)).toString();
|
|
1039
|
-
}
|
|
1040
|
-
function formatAlpha(a) {
|
|
1041
|
-
if (a === 0) return "0";
|
|
1042
|
-
return parseFloat(a.toFixed(4)).toString();
|
|
1043
|
-
}
|
|
1044
|
-
function rgbValuesToColorString(r, g, b, space, alpha) {
|
|
1045
|
-
const alphaSuffix = alpha != null && alpha < 1 ? ` / ${formatAlpha(alpha)}` : "";
|
|
1046
|
-
switch (space) {
|
|
1047
|
-
case "rgb": return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)}${alphaSuffix})`;
|
|
1048
|
-
case "hsl": {
|
|
1049
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
1050
|
-
return `hsl(${formatNum(h, 2)} ${formatNum(s * 100, 2)}% ${formatNum(l * 100, 2)}%${alphaSuffix})`;
|
|
1051
|
-
}
|
|
1052
|
-
case "oklch": {
|
|
1053
|
-
const [L, C, H] = rgbToOklch(r, g, b);
|
|
1054
|
-
return `oklch(${formatNum(L, 5)} ${formatNum(C, 5)} ${formatNum(H, 2)}${alphaSuffix})`;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
function rgbValuesToComponents(r, g, b, space) {
|
|
1059
|
-
switch (space) {
|
|
1060
|
-
case "rgb": return `${formatRgbComponent(r)} ${formatRgbComponent(g)} ${formatRgbComponent(b)}`;
|
|
1061
|
-
case "hsl": {
|
|
1062
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
1063
|
-
return `${formatNum(h, 2)} ${formatNum(s * 100, 2)}% ${formatNum(l * 100, 2)}%`;
|
|
1064
|
-
}
|
|
1065
|
-
case "oklch": {
|
|
1066
|
-
const [L, C, H] = rgbToOklch(r, g, b);
|
|
1067
|
-
return `${formatNum(L, 5)} ${formatNum(C, 5)} ${formatNum(H, 2)}`;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
const parseColorFuncArgs = (str, prefix) => {
|
|
1072
|
-
const start = str.indexOf("(", prefix.length - 1);
|
|
1073
|
-
const end = str.lastIndexOf(")");
|
|
1074
|
-
if (start < 0 || end < 0) return null;
|
|
1075
|
-
const inner = str.slice(start + 1, end).trim();
|
|
1076
|
-
const slashIdx = inner.indexOf("/");
|
|
1077
|
-
let colorPart;
|
|
1078
|
-
let alpha = 1;
|
|
1079
|
-
if (slashIdx >= 0) {
|
|
1080
|
-
colorPart = inner.slice(0, slashIdx);
|
|
1081
|
-
const alphaStr = inner.slice(slashIdx + 1).trim();
|
|
1082
|
-
if (alphaStr) {
|
|
1083
|
-
alpha = alphaStr.endsWith("%") ? parseFloat(alphaStr) / 100 : parseFloat(alphaStr);
|
|
1084
|
-
if (Number.isNaN(alpha)) alpha = 1;
|
|
1085
|
-
}
|
|
1086
|
-
} else colorPart = inner;
|
|
1087
|
-
const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
|
|
1088
|
-
if (parts.length < 3) return null;
|
|
1089
|
-
if (parts.length >= 4 && slashIdx < 0) {
|
|
1090
|
-
const legacyAlpha = parseFloat(parts[3]);
|
|
1091
|
-
if (!Number.isNaN(legacyAlpha)) alpha = legacyAlpha;
|
|
1092
|
-
}
|
|
1093
|
-
return {
|
|
1094
|
-
parts,
|
|
1095
|
-
alpha
|
|
1096
|
-
};
|
|
1097
|
-
};
|
|
1098
|
-
const parseHue = (hueStr) => {
|
|
1099
|
-
let h = parseFloat(hueStr);
|
|
1100
|
-
const lower = hueStr.toLowerCase();
|
|
1101
|
-
if (lower.endsWith("turn")) h = parseFloat(lower) * 360;
|
|
1102
|
-
else if (lower.endsWith("rad")) h = parseFloat(lower) * 180 / Math.PI;
|
|
1103
|
-
return (h % 360 + 360) % 360;
|
|
1104
|
-
};
|
|
1105
|
-
const parsePercent = (val) => {
|
|
1106
|
-
const num = parseFloat(val);
|
|
1107
|
-
return val.includes("%") ? num / 100 : num;
|
|
1108
|
-
};
|
|
1109
|
-
const clamp01 = (n) => Math.max(0, Math.min(1, n));
|
|
1110
|
-
function resolveToRgbaValues(color) {
|
|
1111
|
-
const trimmed = color.trim().toLowerCase();
|
|
1112
|
-
if (trimmed.startsWith("rgb")) {
|
|
1113
|
-
const parsed = parseColorFuncArgs(trimmed, "rgb");
|
|
1114
|
-
if (!parsed || parsed.parts.length < 3) return null;
|
|
1115
|
-
return [
|
|
1116
|
-
parsed.parts[0].endsWith("%") ? parseFloat(parsed.parts[0]) / 100 * 255 : parseFloat(parsed.parts[0]),
|
|
1117
|
-
parsed.parts[1].endsWith("%") ? parseFloat(parsed.parts[1]) / 100 * 255 : parseFloat(parsed.parts[1]),
|
|
1118
|
-
parsed.parts[2].endsWith("%") ? parseFloat(parsed.parts[2]) / 100 * 255 : parseFloat(parsed.parts[2]),
|
|
1119
|
-
parsed.alpha
|
|
1120
|
-
];
|
|
1121
|
-
}
|
|
1122
|
-
if (trimmed.startsWith("#")) return hexToRgbaValues(trimmed);
|
|
1123
|
-
if (trimmed.startsWith("hsl")) {
|
|
1124
|
-
const parsed = parseColorFuncArgs(trimmed, "hsl");
|
|
1125
|
-
if (!parsed) return null;
|
|
1126
|
-
const [r, g, b] = hslToRgbValues(parseHue(parsed.parts[0]), clamp01(parsePercent(parsed.parts[1])), clamp01(parsePercent(parsed.parts[2])));
|
|
1127
|
-
return [
|
|
1128
|
-
r,
|
|
1129
|
-
g,
|
|
1130
|
-
b,
|
|
1131
|
-
parsed.alpha
|
|
1132
|
-
];
|
|
1133
|
-
}
|
|
1134
|
-
if (trimmed.startsWith("oklch(")) {
|
|
1135
|
-
const parsed = parseColorFuncArgs(trimmed, "oklch");
|
|
1136
|
-
if (!parsed) return null;
|
|
1137
|
-
const [r, g, b] = oklchToRgbValues(clamp01(parsePercent(parsed.parts[0])), Math.max(0, parseFloat(parsed.parts[1])), parseHue(parsed.parts[2]));
|
|
1138
|
-
return [
|
|
1139
|
-
r,
|
|
1140
|
-
g,
|
|
1141
|
-
b,
|
|
1142
|
-
parsed.alpha
|
|
1143
|
-
];
|
|
1144
|
-
}
|
|
1145
|
-
const resolved = resolveFunctionColor(trimmed);
|
|
1146
|
-
if (resolved && resolved !== trimmed) return resolveToRgbaValues(resolved);
|
|
1147
|
-
const fallback = strToRgb(trimmed);
|
|
1148
|
-
if (fallback) {
|
|
1149
|
-
if (fallback !== trimmed) return resolveToRgbaValues(fallback);
|
|
1150
|
-
const vals = getRgbValuesFromRgbaString(fallback);
|
|
1151
|
-
if (vals.length >= 3) return [
|
|
1152
|
-
vals[0],
|
|
1153
|
-
vals[1],
|
|
1154
|
-
vals[2],
|
|
1155
|
-
1
|
|
1156
|
-
];
|
|
1157
|
-
}
|
|
1158
|
-
return null;
|
|
1159
|
-
}
|
|
1160
|
-
const SPACE_FUNCS = {
|
|
1161
|
-
rgb: ["rgb", "rgba"],
|
|
1162
|
-
hsl: ["hsl", "hsla"],
|
|
1163
|
-
oklch: ["oklch", "oklcha"]
|
|
1164
|
-
};
|
|
1165
|
-
const CANONICAL_FUNC = {
|
|
1166
|
-
rgb: "rgb",
|
|
1167
|
-
hsl: "hsl",
|
|
1168
|
-
oklch: "oklch"
|
|
1169
|
-
};
|
|
1170
|
-
/**
|
|
1171
|
-
* Parse a native CSS color function ONLY when its name matches the configured
|
|
1172
|
-
* output space. Splits arguments at the top level, preserving every token
|
|
1173
|
-
* (numbers, percentages, `var()`, `calc()`, `min()`, …) verbatim — no numeric
|
|
1174
|
-
* parsing or normalization is performed.
|
|
1175
|
-
*
|
|
1176
|
-
* Returns null when the input is not a same-space function (caller falls back
|
|
1177
|
-
* to the sRGB round-trip for genuine cross-space conversion / hex / named colors).
|
|
1178
|
-
*/
|
|
1179
|
-
function parseSameSpaceFunc(color, space) {
|
|
1180
|
-
const original = color.trim();
|
|
1181
|
-
const lower = original.toLowerCase();
|
|
1182
|
-
const funcs = SPACE_FUNCS[space];
|
|
1183
|
-
let funcName = null;
|
|
1184
|
-
for (const f of funcs) if (lower.startsWith(`${f}(`)) {
|
|
1185
|
-
funcName = f;
|
|
1186
|
-
break;
|
|
1187
|
-
}
|
|
1188
|
-
if (!funcName) return null;
|
|
1189
|
-
const start = funcName.length;
|
|
1190
|
-
const end = original.lastIndexOf(")");
|
|
1191
|
-
if (end < start) return null;
|
|
1192
|
-
const inner = original.slice(start + 1, end).trim();
|
|
1193
|
-
if (!inner) return null;
|
|
1194
|
-
const rawTokens = [];
|
|
1195
|
-
let depth = 0;
|
|
1196
|
-
let buf = "";
|
|
1197
|
-
for (const c of inner) if (c === "(") {
|
|
1198
|
-
depth++;
|
|
1199
|
-
buf += c;
|
|
1200
|
-
} else if (c === ")") {
|
|
1201
|
-
depth--;
|
|
1202
|
-
buf += c;
|
|
1203
|
-
} else if (depth === 0 && (c === " " || c === "," || c === " " || c === "\n")) {
|
|
1204
|
-
if (buf) {
|
|
1205
|
-
rawTokens.push(buf);
|
|
1206
|
-
buf = "";
|
|
1207
|
-
}
|
|
1208
|
-
} else buf += c;
|
|
1209
|
-
if (buf) rawTokens.push(buf);
|
|
1210
|
-
let alpha = null;
|
|
1211
|
-
const parts = [];
|
|
1212
|
-
for (let i = 0; i < rawTokens.length; i++) {
|
|
1213
|
-
const tok = rawTokens[i];
|
|
1214
|
-
if (tok === "/") {
|
|
1215
|
-
alpha = rawTokens[i + 1] ?? null;
|
|
1216
|
-
break;
|
|
1217
|
-
}
|
|
1218
|
-
parts.push(tok);
|
|
1219
|
-
}
|
|
1220
|
-
if (alpha === null && parts.length === 4) {
|
|
1221
|
-
alpha = parts[3];
|
|
1222
|
-
parts.length = 3;
|
|
1223
|
-
}
|
|
1224
|
-
if (parts.length !== 3) return null;
|
|
1225
|
-
if (parts.some((p) => !p)) return null;
|
|
1226
|
-
return {
|
|
1227
|
-
parts,
|
|
1228
|
-
alpha
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
function buildSameSpaceString(parsed, space) {
|
|
1232
|
-
const func = CANONICAL_FUNC[space];
|
|
1233
|
-
const body = parsed.parts.join(" ");
|
|
1234
|
-
return parsed.alpha != null ? `${func}(${body} / ${parsed.alpha})` : `${func}(${body})`;
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Build the decomposed component list for a same-space value.
|
|
1238
|
-
*
|
|
1239
|
-
* Dynamic tokens (`var()` / `calc()` / …) are preserved verbatim — they can't
|
|
1240
|
-
* be parsed and must survive into the CSS output. Purely static components are
|
|
1241
|
-
* normalized to the canonical numeric form for the space so the companion
|
|
1242
|
-
* `--*-color-{space}` variable stays a valid `<number>+` value (rgb/oklch) —
|
|
1243
|
-
* WITHOUT round-tripping through sRGB, which would clamp wide-gamut oklch.
|
|
1244
|
-
*
|
|
1245
|
-
* Non-native, parse-time-only functions (`okhsl()` / `okhst()`) are always
|
|
1246
|
-
* resolved to a static `rgb(...%)` upstream, so this is the path that turns
|
|
1247
|
-
* their percentage channels back into 0-255 numbers.
|
|
1248
|
-
*/
|
|
1249
|
-
function buildSameSpaceComponents(parsed, space) {
|
|
1250
|
-
const parts = parsed.parts;
|
|
1251
|
-
if (parts.some((p) => p.includes("("))) return parts.join(" ");
|
|
1252
|
-
switch (space) {
|
|
1253
|
-
case "rgb": return parts.map((p) => p.endsWith("%") ? formatRgbComponent(parseFloat(p) / 100 * 255) : p).join(" ");
|
|
1254
|
-
case "hsl": return parts.join(" ");
|
|
1255
|
-
case "oklch": {
|
|
1256
|
-
const [l, c, h] = parts;
|
|
1257
|
-
return [
|
|
1258
|
-
l.endsWith("%") ? formatNum(parseFloat(l) / 100, 5) : l,
|
|
1259
|
-
c,
|
|
1260
|
-
h
|
|
1261
|
-
].join(" ");
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Convert any supported color string to the configured color space CSS format.
|
|
1267
|
-
* Preserves alpha channel when present in the input.
|
|
1268
|
-
* Returns null if the input cannot be parsed.
|
|
1269
|
-
*/
|
|
1270
|
-
function strToColorSpace(color) {
|
|
1271
|
-
if (!color) return void 0;
|
|
1272
|
-
const cached = colorSpaceCache.get(color);
|
|
1273
|
-
if (cached !== void 0) return cached;
|
|
1274
|
-
const sameSpace = parseSameSpaceFunc(color, currentColorSpace);
|
|
1275
|
-
if (sameSpace) {
|
|
1276
|
-
const result = buildSameSpaceString(sameSpace, currentColorSpace);
|
|
1277
|
-
colorSpaceCache.set(color, result);
|
|
1278
|
-
return result;
|
|
1279
|
-
}
|
|
1280
|
-
const rgba = resolveToRgbaValues(color);
|
|
1281
|
-
if (!rgba) {
|
|
1282
|
-
colorSpaceCache.set(color, null);
|
|
1283
|
-
return null;
|
|
1284
|
-
}
|
|
1285
|
-
const result = rgbValuesToColorString(rgba[0], rgba[1], rgba[2], currentColorSpace, rgba[3]);
|
|
1286
|
-
colorSpaceCache.set(color, result);
|
|
1287
|
-
return result;
|
|
1288
|
-
}
|
|
1289
731
|
/**
|
|
1290
732
|
* Set the alpha of a whole color, replacing any it already carries.
|
|
1291
733
|
*
|
|
@@ -1293,14 +735,13 @@ function strToColorSpace(color) {
|
|
|
1293
735
|
* emits. `oklch(from <color> l c h / <alpha>)` copies the channels over and
|
|
1294
736
|
* writes the alpha slot, which needs nothing from the color except that it *is*
|
|
1295
737
|
* a color: a `color-mix()`, a `light-dark()`, a `currentcolor`, a
|
|
1296
|
-
* `--name-color` written by hand-authored CSS
|
|
1297
|
-
*
|
|
1298
|
-
* the engine
|
|
738
|
+
* `--name-color` written by hand-authored CSS that Tasty never defined — all of
|
|
739
|
+
* them work, where writing into a channel slot directly would require channels
|
|
740
|
+
* the engine has no way to compute.
|
|
1299
741
|
*
|
|
1300
742
|
* Alpha is *replaced*, not composed. A token holding `rgb(255 0 0 / .8)` faded
|
|
1301
|
-
* to `.5` is alpha `.5
|
|
1302
|
-
*
|
|
1303
|
-
* cannot do this — it would multiply the two to `.4`.
|
|
743
|
+
* to `.5` is alpha `.5`. `color-mix()` against `transparent` cannot do this —
|
|
744
|
+
* it would multiply the two to `.4`.
|
|
1304
745
|
*
|
|
1305
746
|
* The alpha slot takes a `<number>` or a `<percentage>`, so an opacity custom
|
|
1306
747
|
* property passes straight through in whichever form the author declared it.
|
|
@@ -1350,115 +791,6 @@ function parseAlphaOverride(value) {
|
|
|
1350
791
|
alpha: match[2]
|
|
1351
792
|
} : null;
|
|
1352
793
|
}
|
|
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
|
-
/**
|
|
1375
|
-
* Extract the decomposed components of a color in the configured color space.
|
|
1376
|
-
* Returns a space-separated string of components without the wrapping function.
|
|
1377
|
-
* Alpha is NOT included — components are used for alpha composition via `/ alpha`.
|
|
1378
|
-
*/
|
|
1379
|
-
function getColorSpaceComponents(color) {
|
|
1380
|
-
const cached = componentsCache.get(color);
|
|
1381
|
-
if (cached !== void 0) return cached;
|
|
1382
|
-
const sameSpace = parseSameSpaceFunc(color, currentColorSpace);
|
|
1383
|
-
if (sameSpace) {
|
|
1384
|
-
const result = buildSameSpaceComponents(sameSpace, currentColorSpace);
|
|
1385
|
-
componentsCache.set(color, result);
|
|
1386
|
-
return result;
|
|
1387
|
-
}
|
|
1388
|
-
const rgba = resolveToRgbaValues(color);
|
|
1389
|
-
if (!rgba) return color;
|
|
1390
|
-
const result = rgbValuesToComponents(rgba[0], rgba[1], rgba[2], currentColorSpace);
|
|
1391
|
-
componentsCache.set(color, result);
|
|
1392
|
-
return result;
|
|
1393
|
-
}
|
|
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
|
-
/**
|
|
1430
|
-
* Convert a color initial value (from @property definitions) to components
|
|
1431
|
-
* in the configured color space.
|
|
1432
|
-
*/
|
|
1433
|
-
function colorInitialValueToComponents(initialValue) {
|
|
1434
|
-
if (initialValue == null) return getDefaultComponents();
|
|
1435
|
-
const val = String(initialValue).trim().toLowerCase();
|
|
1436
|
-
if (val === "transparent" || val === "rgba(0,0,0,0)" || val === "") return getDefaultComponents();
|
|
1437
|
-
if (val === "white") return rgbValuesToComponents(255, 255, 255, currentColorSpace);
|
|
1438
|
-
if (val === "black") return rgbValuesToComponents(0, 0, 0, currentColorSpace);
|
|
1439
|
-
const rgbMatch = val.match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/);
|
|
1440
|
-
if (rgbMatch) return rgbValuesToComponents(parseInt(rgbMatch[1]), parseInt(rgbMatch[2]), parseInt(rgbMatch[3]), currentColorSpace);
|
|
1441
|
-
return getDefaultComponents();
|
|
1442
|
-
}
|
|
1443
|
-
function getDefaultComponents() {
|
|
1444
|
-
switch (currentColorSpace) {
|
|
1445
|
-
case "rgb": return "0 0 0";
|
|
1446
|
-
case "hsl": return "0 0% 0%";
|
|
1447
|
-
case "oklch": return "0 0 0";
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Get the CSS @property syntax for the companion components variable.
|
|
1452
|
-
* RGB and OKLCH components are all plain numbers, so `<number>+` works.
|
|
1453
|
-
* HSL includes percentages (`h s% l%`), so `*` is the only safe choice.
|
|
1454
|
-
*/
|
|
1455
|
-
function getComponentPropertySyntax() {
|
|
1456
|
-
switch (currentColorSpace) {
|
|
1457
|
-
case "rgb":
|
|
1458
|
-
case "oklch": return "<number>+";
|
|
1459
|
-
default: return "*";
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
794
|
//#endregion
|
|
1463
795
|
//#region src/parser/types.ts
|
|
1464
796
|
const makeEmptyPart = () => ({
|
|
@@ -1644,7 +976,7 @@ function classify(raw, opts, recurse) {
|
|
|
1644
976
|
}
|
|
1645
977
|
if (token === "#current") return {
|
|
1646
978
|
bucket: 0,
|
|
1647
|
-
processed: "
|
|
979
|
+
processed: "var(--current-color)"
|
|
1648
980
|
};
|
|
1649
981
|
const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
|
|
1650
982
|
if (currentAlphaMatch) return {
|
|
@@ -1878,6 +1210,113 @@ function classify(raw, opts, recurse) {
|
|
|
1878
1210
|
};
|
|
1879
1211
|
}
|
|
1880
1212
|
//#endregion
|
|
1213
|
+
//#region src/parser/lru.ts
|
|
1214
|
+
var Lru = class {
|
|
1215
|
+
limit;
|
|
1216
|
+
map = /* @__PURE__ */ new Map();
|
|
1217
|
+
head = null;
|
|
1218
|
+
tail = null;
|
|
1219
|
+
onEvict;
|
|
1220
|
+
constructor(limit = 1e3, onEvict) {
|
|
1221
|
+
this.limit = limit;
|
|
1222
|
+
let normalized = Number.isFinite(this.limit) ? Math.floor(this.limit) : 1e3;
|
|
1223
|
+
if (normalized <= 0) normalized = 1e3;
|
|
1224
|
+
this.limit = normalized;
|
|
1225
|
+
this.onEvict = onEvict;
|
|
1226
|
+
}
|
|
1227
|
+
setOnEvict(fn) {
|
|
1228
|
+
this.onEvict = fn;
|
|
1229
|
+
}
|
|
1230
|
+
get(key) {
|
|
1231
|
+
const node = this.map.get(key);
|
|
1232
|
+
if (!node) return void 0;
|
|
1233
|
+
this.touch(key, node);
|
|
1234
|
+
return node.value;
|
|
1235
|
+
}
|
|
1236
|
+
set(key, value) {
|
|
1237
|
+
let node = this.map.get(key);
|
|
1238
|
+
if (node) {
|
|
1239
|
+
node.value = value;
|
|
1240
|
+
this.touch(key, node);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
node = {
|
|
1244
|
+
prev: null,
|
|
1245
|
+
next: this.head,
|
|
1246
|
+
value
|
|
1247
|
+
};
|
|
1248
|
+
if (this.head) {
|
|
1249
|
+
const headNode = this.map.get(this.head);
|
|
1250
|
+
if (headNode) headNode.prev = key;
|
|
1251
|
+
}
|
|
1252
|
+
this.head = key;
|
|
1253
|
+
if (!this.tail) this.tail = key;
|
|
1254
|
+
this.map.set(key, node);
|
|
1255
|
+
if (this.map.size > this.limit) this.evict();
|
|
1256
|
+
}
|
|
1257
|
+
delete(key) {
|
|
1258
|
+
const node = this.map.get(key);
|
|
1259
|
+
if (!node) return;
|
|
1260
|
+
if (node.prev) {
|
|
1261
|
+
const prevNode = this.map.get(node.prev);
|
|
1262
|
+
if (prevNode) prevNode.next = node.next;
|
|
1263
|
+
}
|
|
1264
|
+
if (node.next) {
|
|
1265
|
+
const nextNode = this.map.get(node.next);
|
|
1266
|
+
if (nextNode) nextNode.prev = node.prev;
|
|
1267
|
+
}
|
|
1268
|
+
if (this.head === key) this.head = node.next;
|
|
1269
|
+
if (this.tail === key) this.tail = node.prev;
|
|
1270
|
+
this.map.delete(key);
|
|
1271
|
+
}
|
|
1272
|
+
keys() {
|
|
1273
|
+
return this.map.keys();
|
|
1274
|
+
}
|
|
1275
|
+
touch(key, node) {
|
|
1276
|
+
if (this.head === key) return;
|
|
1277
|
+
if (node.prev) {
|
|
1278
|
+
const prevNode = this.map.get(node.prev);
|
|
1279
|
+
if (prevNode) prevNode.next = node.next;
|
|
1280
|
+
}
|
|
1281
|
+
if (node.next) {
|
|
1282
|
+
const nextNode = this.map.get(node.next);
|
|
1283
|
+
if (nextNode) nextNode.prev = node.prev;
|
|
1284
|
+
}
|
|
1285
|
+
if (this.tail === key) this.tail = node.prev;
|
|
1286
|
+
node.prev = null;
|
|
1287
|
+
node.next = this.head;
|
|
1288
|
+
if (this.head) {
|
|
1289
|
+
const headNode = this.map.get(this.head);
|
|
1290
|
+
if (headNode) headNode.prev = key;
|
|
1291
|
+
}
|
|
1292
|
+
this.head = key;
|
|
1293
|
+
}
|
|
1294
|
+
evict() {
|
|
1295
|
+
const old = this.tail;
|
|
1296
|
+
if (!old) return;
|
|
1297
|
+
const node = this.map.get(old);
|
|
1298
|
+
if (!node) {
|
|
1299
|
+
if (this.head === old) this.head = null;
|
|
1300
|
+
this.tail = null;
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
if (node.prev) {
|
|
1304
|
+
const prevNode = this.map.get(node.prev);
|
|
1305
|
+
if (prevNode) prevNode.next = null;
|
|
1306
|
+
}
|
|
1307
|
+
this.tail = node.prev;
|
|
1308
|
+
if (this.head === old) this.head = null;
|
|
1309
|
+
this.map.delete(old);
|
|
1310
|
+
if (this.onEvict) try {
|
|
1311
|
+
this.onEvict(old, node.value);
|
|
1312
|
+
} catch {}
|
|
1313
|
+
}
|
|
1314
|
+
clear() {
|
|
1315
|
+
this.map.clear();
|
|
1316
|
+
this.head = this.tail = null;
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
//#endregion
|
|
1881
1320
|
//#region src/parser/tokenizer.ts
|
|
1882
1321
|
const isWhitespace = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
|
|
1883
1322
|
function scan(src, cb) {
|
|
@@ -2072,6 +1511,116 @@ function isDevEnv() {
|
|
|
2072
1511
|
return true;
|
|
2073
1512
|
}
|
|
2074
1513
|
//#endregion
|
|
1514
|
+
//#region src/styles/shared.ts
|
|
1515
|
+
/**
|
|
1516
|
+
* If the group contains exactly one value that is a CSS-wide keyword
|
|
1517
|
+
* and no colors, return the keyword. Otherwise null.
|
|
1518
|
+
*
|
|
1519
|
+
* Direction mods (top/right/bottom/left) may coexist with the keyword
|
|
1520
|
+
* for directional application like `padding="inherit top"`.
|
|
1521
|
+
*/
|
|
1522
|
+
function extractCSSWideKeyword(group) {
|
|
1523
|
+
if (group.values.length !== 1 || group.colors.length > 0) return null;
|
|
1524
|
+
return CSS_WIDE_KEYWORDS.has(group.values[0]) ? group.values[0] : null;
|
|
1525
|
+
}
|
|
1526
|
+
/** A resolved custom-property reference, i.e. `var(--name)` or `var(--name, …)`. */
|
|
1527
|
+
const RE_VAR_REFERENCE = /^var\(/;
|
|
1528
|
+
/**
|
|
1529
|
+
* Assign custom-property references to the style and color slots of a
|
|
1530
|
+
* `<line-width> <line-style> <line-color>` shorthand (`border`, `outline`).
|
|
1531
|
+
*
|
|
1532
|
+
* The parser cannot type a reference, so it buckets `$name` as a plain value and
|
|
1533
|
+
* the handler has to place it. A reference fills the first slot still free, in
|
|
1534
|
+
* shorthand order: the width takes `values[0]`, then the style, then the color.
|
|
1535
|
+
* The style would otherwise arrive as a keyword (`solid`, `dashed`, …) that a
|
|
1536
|
+
* reference has no way to match, and the color as `#name` — the `$name-color`
|
|
1537
|
+
* form the parser buckets as a color exists to reference a raw CSS custom
|
|
1538
|
+
* property, not as the way colors are written, so a reference should not reach
|
|
1539
|
+
* for the color slot until the style slot is taken.
|
|
1540
|
+
*
|
|
1541
|
+
* Lengths are left alone: a second length is not valid in these shorthands, and
|
|
1542
|
+
* promoting one would emit an invalid declaration instead of ignoring an extra
|
|
1543
|
+
* value. Callers supply their own slot defaults.
|
|
1544
|
+
*/
|
|
1545
|
+
function assignLineSlots(values, styleKeyword, colorToken) {
|
|
1546
|
+
if (styleKeyword && colorToken) return {
|
|
1547
|
+
style: styleKeyword,
|
|
1548
|
+
color: colorToken
|
|
1549
|
+
};
|
|
1550
|
+
const spare = values.slice(1).filter((value) => RE_VAR_REFERENCE.test(value) && value !== colorToken);
|
|
1551
|
+
return {
|
|
1552
|
+
style: styleKeyword || spare.shift(),
|
|
1553
|
+
color: colorToken || spare.shift()
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
/** Warning keys already emitted, so each distinct offending value warns once. */
|
|
1557
|
+
const emittedWarnings$2 = /* @__PURE__ */ new Set();
|
|
1558
|
+
/** A resolved custom-property reference anywhere in a token. */
|
|
1559
|
+
const RE_HAS_VAR_REFERENCE = /var\(/;
|
|
1560
|
+
/**
|
|
1561
|
+
* Reject a custom-property reference used where a *token name* is expected.
|
|
1562
|
+
*
|
|
1563
|
+
* `preset` and `transition` take the name of a design token (`t3`, `fill`) and
|
|
1564
|
+
* interpolate it into a CSS custom property — `var(--t3-font-size)`. A reference
|
|
1565
|
+
* substituted into that position builds `var(--var(--x)-font-size)`, which is not
|
|
1566
|
+
* a valid custom-property name, so the browser drops the declaration and the
|
|
1567
|
+
* style silently does nothing. The DSL has no way to indirect a token name
|
|
1568
|
+
* through a custom property: the name is needed at build time, and a reference
|
|
1569
|
+
* only resolves in the browser.
|
|
1570
|
+
*
|
|
1571
|
+
* Returns true (warning once per value in dev) so the caller can fall back
|
|
1572
|
+
* instead of emitting a declaration that cannot work.
|
|
1573
|
+
*/
|
|
1574
|
+
function isTokenNameReference(property, name) {
|
|
1575
|
+
if (!RE_HAS_VAR_REFERENCE.test(name)) return false;
|
|
1576
|
+
warnOnceDev(`token-name-reference:${property}:${name}`, `${property}="${name}": a custom property cannot name a token. The name is interpolated into a CSS custom property at build time, so a reference would build an unusable name. It is ignored.`);
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Emit a style-level warning at most once per `key`. No-op outside dev mode.
|
|
1581
|
+
*
|
|
1582
|
+
* Style handlers run once per state combination and their results are cached,
|
|
1583
|
+
* but the same value can arrive from many components, so dedupe is required.
|
|
1584
|
+
*
|
|
1585
|
+
* `isDevEnv()` is called lazily rather than captured at module load (the pattern
|
|
1586
|
+
* used in `config.ts` and `states/index.ts`) so that tests can enable it with
|
|
1587
|
+
* `vi.stubEnv('NODE_ENV', 'development')` — `isDevEnv()` returns `false` for
|
|
1588
|
+
* `NODE_ENV=test`, which is why every module-load-gated warning in this repo is
|
|
1589
|
+
* currently untestable. The key is registered *before* the dev check, so
|
|
1590
|
+
* production pays at most one `isDevEnv()` call per distinct offending value and
|
|
1591
|
+
* never touches the console.
|
|
1592
|
+
*/
|
|
1593
|
+
function warnOnceDev(key, message) {
|
|
1594
|
+
if (emittedWarnings$2.has(key)) return;
|
|
1595
|
+
emittedWarnings$2.add(key);
|
|
1596
|
+
if (!isDevEnv()) return;
|
|
1597
|
+
console.warn(`[Tasty] ${message}`);
|
|
1598
|
+
}
|
|
1599
|
+
/** Forget emitted style warnings. Called by `resetConfig()`; test isolation. */
|
|
1600
|
+
function resetStyleWarnings() {
|
|
1601
|
+
emittedWarnings$2.clear();
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Report a comma group that names direction modifiers together with more values
|
|
1605
|
+
* than the group can use.
|
|
1606
|
+
*
|
|
1607
|
+
* The parser buckets values and modifiers into separate arrays per comma group,
|
|
1608
|
+
* so `padding: '2x 4x top right'`, `padding: '2x top 4x right'` and
|
|
1609
|
+
* `padding: 'top 2x right 4x'` all reach a handler identically — the pairing a
|
|
1610
|
+
* reader infers from source order does not survive parsing. Such a group
|
|
1611
|
+
* therefore carries a single value, applied to every direction it names.
|
|
1612
|
+
*
|
|
1613
|
+
* `maxValues` is 2 only for a span modifier (`inset: '2x 4x bottom dock'`),
|
|
1614
|
+
* where the second value insets the sides the dock spans.
|
|
1615
|
+
*
|
|
1616
|
+
* Callers must guard on the count themselves so the valid path costs one integer
|
|
1617
|
+
* comparison; everything expensive happens here, on the misuse path only.
|
|
1618
|
+
*/
|
|
1619
|
+
function warnExtraGroupValues(property, input, maxValues) {
|
|
1620
|
+
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".`;
|
|
1621
|
+
warnOnceDev(`extra-values:${property}:${input}`, message);
|
|
1622
|
+
}
|
|
1623
|
+
//#endregion
|
|
2075
1624
|
//#region src/plugins/color-func.ts
|
|
2076
1625
|
const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
|
|
2077
1626
|
/**
|
|
@@ -2089,7 +1638,9 @@ const parseAngle = (value) => {
|
|
|
2089
1638
|
}
|
|
2090
1639
|
};
|
|
2091
1640
|
/**
|
|
2092
|
-
* Parse a percentage value (e.g., "50%") to a 0-1 range.
|
|
1641
|
+
* Parse a percentage value (e.g., "50%") to a 0-1 range. A unitless number is
|
|
1642
|
+
* read as the factor it already looks like, so `okhsl(280 .8 .52)` and
|
|
1643
|
+
* `okhsl(280 80% 52%)` are the same color.
|
|
2093
1644
|
*/
|
|
2094
1645
|
const parsePercentage = (value) => {
|
|
2095
1646
|
const match = value.match(/^([+-]?\d*\.?\d+)%?$/);
|
|
@@ -2128,6 +1679,8 @@ function createColorFunc(name, convert, label) {
|
|
|
2128
1679
|
const h = parseAngle(tokens[0]);
|
|
2129
1680
|
const c2 = parsePercentage(tokens[1]);
|
|
2130
1681
|
const c3 = parsePercentage(tokens[2]);
|
|
1682
|
+
const misscaled = [tokens[1], tokens[2]].filter((raw, i) => !raw.includes("%") && (i === 0 ? c2 : c3) > 1);
|
|
1683
|
+
if (misscaled.length) warnOnceDev(`${name}:percent-scale`, `${name}(): channels are factors (0-1) or percentages, so ${misscaled.map((v) => `"${v}"`).join(" and ")} ${misscaled.length > 1 ? "clamp" : "clamps"} to 1 — add the missing '%', or write the value as a 0-1 number.`);
|
|
2131
1684
|
const [r, g, b] = convert(h, clamp(c2, 0, 1), clamp(c3, 0, 1));
|
|
2132
1685
|
const format = (n) => {
|
|
2133
1686
|
const pct = n * 100;
|
|
@@ -2264,22 +1817,35 @@ function normalizeColorTokenValue(value) {
|
|
|
2264
1817
|
return value;
|
|
2265
1818
|
}
|
|
2266
1819
|
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)[,)]/;
|
|
2268
1820
|
const RGB_ALPHA_PATTERN = /\/\s*([0-9.]+)\)/;
|
|
2269
1821
|
const RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
|
|
2270
1822
|
const RE_VAR_COLOR = /^var\(--[a-z0-9-]+-color/;
|
|
2271
1823
|
function isSimpleColorFast(val) {
|
|
2272
1824
|
switch (val.charCodeAt(0)) {
|
|
2273
1825
|
case 35: return RE_HEX_COLOR.test(val);
|
|
2274
|
-
case 114:
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
case
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
case
|
|
2281
|
-
|
|
1826
|
+
case 114:
|
|
1827
|
+
if (val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98) return true;
|
|
1828
|
+
break;
|
|
1829
|
+
case 104:
|
|
1830
|
+
if (val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108) return true;
|
|
1831
|
+
break;
|
|
1832
|
+
case 108:
|
|
1833
|
+
if (val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104) return true;
|
|
1834
|
+
break;
|
|
1835
|
+
case 111:
|
|
1836
|
+
if (val.startsWith("oklch(")) return true;
|
|
1837
|
+
break;
|
|
1838
|
+
case 118:
|
|
1839
|
+
if (RE_VAR_COLOR.test(val)) return true;
|
|
1840
|
+
break;
|
|
1841
|
+
case 99:
|
|
1842
|
+
if (val === "currentColor" || val === "currentcolor") return true;
|
|
1843
|
+
break;
|
|
1844
|
+
case 116:
|
|
1845
|
+
if (val === "transparent") return true;
|
|
1846
|
+
break;
|
|
2282
1847
|
}
|
|
1848
|
+
return getNamedColorHex().has(val.toLowerCase());
|
|
2283
1849
|
}
|
|
2284
1850
|
let colorWarningCount = 0;
|
|
2285
1851
|
const MAX_COLOR_WARNINGS = 10;
|
|
@@ -2456,8 +2022,7 @@ function parseColor(val, ignoreError = false) {
|
|
|
2456
2022
|
}
|
|
2457
2023
|
const faded = parseAlphaOverride(firstColor);
|
|
2458
2024
|
const baseColor = faded ? faded.color : firstColor;
|
|
2459
|
-
|
|
2460
|
-
if (!nameMatch) nameMatch = baseColor.match(COLOR_VAR_COMPONENTS_PATTERN);
|
|
2025
|
+
const nameMatch = baseColor.match(COLOR_VAR_PATTERN);
|
|
2461
2026
|
let opacity;
|
|
2462
2027
|
if (faded) {
|
|
2463
2028
|
const { alpha } = faded;
|
|
@@ -3224,7 +2789,6 @@ function getEffectiveDefinition(token, userDefinition) {
|
|
|
3224
2789
|
if (!parsed.isValid) return {
|
|
3225
2790
|
cssName: "",
|
|
3226
2791
|
definition: userDefinition,
|
|
3227
|
-
isColor: false,
|
|
3228
2792
|
isValid: false,
|
|
3229
2793
|
error: parsed.error
|
|
3230
2794
|
};
|
|
@@ -3235,13 +2799,11 @@ function getEffectiveDefinition(token, userDefinition) {
|
|
|
3235
2799
|
inherits: userDefinition.inherits,
|
|
3236
2800
|
initialValue: userDefinition.initialValue ?? "transparent"
|
|
3237
2801
|
},
|
|
3238
|
-
isColor: true,
|
|
3239
2802
|
isValid: true
|
|
3240
2803
|
};
|
|
3241
2804
|
return {
|
|
3242
2805
|
cssName: parsed.cssName,
|
|
3243
2806
|
definition: userDefinition,
|
|
3244
|
-
isColor: false,
|
|
3245
2807
|
isValid: true
|
|
3246
2808
|
};
|
|
3247
2809
|
}
|
|
@@ -3396,31 +2958,242 @@ function makeKeyframeName(prefix, suffix) {
|
|
|
3396
2958
|
return `${prefix}k${suffix}`;
|
|
3397
2959
|
}
|
|
3398
2960
|
/**
|
|
3399
|
-
* Build a counter-style name: `${prefix}c${suffix}`.
|
|
3400
|
-
* The `c` discriminator keeps counter-style names visually distinct
|
|
3401
|
-
* from class names sharing the same prefix.
|
|
2961
|
+
* Build a counter-style name: `${prefix}c${suffix}`.
|
|
2962
|
+
* The `c` discriminator keeps counter-style names visually distinct
|
|
2963
|
+
* from class names sharing the same prefix.
|
|
2964
|
+
*/
|
|
2965
|
+
function makeCounterStyleName(prefix, suffix) {
|
|
2966
|
+
return `${prefix}c${suffix}`;
|
|
2967
|
+
}
|
|
2968
|
+
/** Escape a string for safe inclusion in a regex literal. */
|
|
2969
|
+
function escapeRegex(str) {
|
|
2970
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Regex matching any tasty class for the given prefix.
|
|
2974
|
+
* Used by the runtime GC's DOM scan and class-allocation bookkeeping.
|
|
2975
|
+
*/
|
|
2976
|
+
function tastyClassRegex(prefix) {
|
|
2977
|
+
return new RegExp(`^${escapeRegex(prefix)}[a-z0-9]+$`);
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Global regex extracting tasty class names from RSC-inlined CSS.
|
|
2981
|
+
* Looks for the doubled-specificity pattern `.cls.cls` that
|
|
2982
|
+
* `formatRules()` always emits, which makes extraction reliable.
|
|
2983
|
+
*/
|
|
2984
|
+
function rscClassRegexGlobal(prefix) {
|
|
2985
|
+
return new RegExp(`\\.(${escapeRegex(prefix)}[a-z0-9]+)\\.\\1`, "g");
|
|
2986
|
+
}
|
|
2987
|
+
//#endregion
|
|
2988
|
+
//#region src/utils/warnings.ts
|
|
2989
|
+
const PREFIX = "[Tasty]";
|
|
2990
|
+
function warn(...args) {
|
|
2991
|
+
console.warn(PREFIX, ...args);
|
|
2992
|
+
}
|
|
2993
|
+
//#endregion
|
|
2994
|
+
//#region src/injector/batch.ts
|
|
2995
|
+
/**
|
|
2996
|
+
* Deferred CSSOM writes ("batched injection").
|
|
2997
|
+
*
|
|
2998
|
+
* Every `insertRule()` on a live stylesheet invalidates style for the sheet's
|
|
2999
|
+
* scope, so Blink recalculates style the next time anything reads layout or
|
|
3000
|
+
* computed style. When components inject during React's render phase and other
|
|
3001
|
+
* components read layout in the same pass, the two interleave:
|
|
3002
|
+
*
|
|
3003
|
+
* inject -> read (forced recalc) -> inject -> read (forced recalc) -> ...
|
|
3004
|
+
*
|
|
3005
|
+
* Batching moves every sheet write out from between those reads. Writes are
|
|
3006
|
+
* queued in FIFO order and drained in one go, so the tree is invalidated once
|
|
3007
|
+
* per flush instead of once per component.
|
|
3008
|
+
*
|
|
3009
|
+
* ## Ordering
|
|
3010
|
+
*
|
|
3011
|
+
* A single queue holds *all* writes — component rules, global rules, keyframes,
|
|
3012
|
+
* `@property`, `@font-face`, `@counter-style`, `@function` and raw CSS. Draining
|
|
3013
|
+
* it in insertion order keeps the sheet byte-identical to unbatched output,
|
|
3014
|
+
* which matters because equal-specificity rules resolve by document order.
|
|
3015
|
+
*
|
|
3016
|
+
* ## Batch windows — why queuing is safe
|
|
3017
|
+
*
|
|
3018
|
+
* Deferring a write past React's layout phase would let a `useLayoutEffect`
|
|
3019
|
+
* measure an element whose rules are not in the sheet yet, reading the unstyled
|
|
3020
|
+
* box. `<TastyBatchProvider>` closes that hole by opening a *window* during its
|
|
3021
|
+
* render and closing it — flushing — in its `useInsertionEffect`, which React
|
|
3022
|
+
* runs in the mutation phase, before any layout effect:
|
|
3023
|
+
*
|
|
3024
|
+
* provider renders -> window OPEN
|
|
3025
|
+
* children render -> injections queued
|
|
3026
|
+
* provider insertionEffect -> FLUSH, window CLOSED
|
|
3027
|
+
* layout effects run -> rules are in the sheet
|
|
3028
|
+
*
|
|
3029
|
+
* So in the default (`batchInjection: true`) mode a write is only ever queued
|
|
3030
|
+
* inside a commit whose flush is already guaranteed by that same commit. Every
|
|
3031
|
+
* injection outside a window — a deep update the provider did not re-render
|
|
3032
|
+
* for, a `useLayoutEffect` that injects, an event handler, an async callback —
|
|
3033
|
+
* is written straight through, exactly as with batching off.
|
|
3034
|
+
*
|
|
3035
|
+
* `batchInjection: 'always'` opts out of the gate and queues unconditionally.
|
|
3036
|
+
* That wins on more commits, at the cost of the measurement hazard above.
|
|
3037
|
+
*
|
|
3038
|
+
* ## Flush points (earliest wins)
|
|
3039
|
+
*
|
|
3040
|
+
* 1. `<TastyBatchProvider>`'s `useInsertionEffect` — closes the window.
|
|
3041
|
+
* 2. A microtask — a backstop for a render that was aborted or suspended and
|
|
3042
|
+
* therefore never reached its insertion effect. An aborted render mounts
|
|
3043
|
+
* nothing, so nothing can measure what it queued. Microtasks also always
|
|
3044
|
+
* drain before paint, so styles are never visually missing.
|
|
3045
|
+
* 3. `flushStyles()` — explicit, and called internally by every injector read
|
|
3046
|
+
* API (`getCSSText`, `cleanup`, `gc`, `destroy`, ...).
|
|
3047
|
+
*/
|
|
3048
|
+
/**
|
|
3049
|
+
* FIFO of pending writes. Drained with a moving `head` index rather than
|
|
3050
|
+
* `shift()` so a large batch stays O(n) instead of O(n²).
|
|
3051
|
+
*/
|
|
3052
|
+
let queue = [];
|
|
3053
|
+
let head = 0;
|
|
3054
|
+
let microtaskScheduled = false;
|
|
3055
|
+
let flushing = false;
|
|
3056
|
+
/**
|
|
3057
|
+
* Whether a `<TastyBatchProvider>` has rendered in the current commit and will
|
|
3058
|
+
* therefore flush in its insertion effect, making it safe to queue a write.
|
|
3059
|
+
*
|
|
3060
|
+
* A flag rather than a depth count, because renders and insertion effects do
|
|
3061
|
+
* not pair up one-to-one. StrictMode double-invokes render but runs the
|
|
3062
|
+
* insertion effect once, so a counter ends the commit stuck above zero — and a
|
|
3063
|
+
* window that stays open past its commit turns the next provider-less commit
|
|
3064
|
+
* into `'always'` mode behind the user's back, which is exactly the measurement
|
|
3065
|
+
* hazard `true` exists to avoid.
|
|
3066
|
+
*
|
|
3067
|
+
* Clearing on the first close is safe with nested or sibling providers: React
|
|
3068
|
+
* finishes every render in a commit before running any insertion effect, so once
|
|
3069
|
+
* one closes, no further render-phase injection can arrive in that commit.
|
|
3070
|
+
*/
|
|
3071
|
+
let windowOpen = false;
|
|
3072
|
+
/** Whether any window has ever been opened, i.e. a provider is in the tree. */
|
|
3073
|
+
let everOpened = false;
|
|
3074
|
+
/** Dev-only: warn at most once that batching is on with no provider mounted. */
|
|
3075
|
+
let warnedNoProvider = false;
|
|
3076
|
+
/**
|
|
3077
|
+
* Queue a sheet write.
|
|
3078
|
+
*
|
|
3079
|
+
* Returns a handle whose `cancel()` drops the write if the caller disposes
|
|
3080
|
+
* before the flush. The handle is intentionally tiny — one is allocated per
|
|
3081
|
+
* injected class, on the cache-miss path only.
|
|
3082
|
+
*/
|
|
3083
|
+
function enqueueStyleWrite(run) {
|
|
3084
|
+
if (flushing) {
|
|
3085
|
+
run();
|
|
3086
|
+
return {
|
|
3087
|
+
run,
|
|
3088
|
+
cancelled: false,
|
|
3089
|
+
done: true
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
const entry = {
|
|
3093
|
+
run,
|
|
3094
|
+
cancelled: false,
|
|
3095
|
+
done: false
|
|
3096
|
+
};
|
|
3097
|
+
queue.push(entry);
|
|
3098
|
+
scheduleMicrotaskFlush();
|
|
3099
|
+
return entry;
|
|
3100
|
+
}
|
|
3101
|
+
/** Whether any write is still waiting to hit a stylesheet. */
|
|
3102
|
+
function hasPendingStyleWrites() {
|
|
3103
|
+
return head < queue.length;
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Open a batch window. Called from `<TastyBatchProvider>`'s render, so every
|
|
3107
|
+
* descendant injection in this commit is covered by the provider's insertion
|
|
3108
|
+
* effect.
|
|
3109
|
+
*
|
|
3110
|
+
* Called during render on purpose: the window must be open before children
|
|
3111
|
+
* render. It is idempotent — so StrictMode's double render costs nothing — and
|
|
3112
|
+
* carries no other side effect. A render that is thrown away is recovered by the
|
|
3113
|
+
* microtask backstop.
|
|
3114
|
+
*
|
|
3115
|
+
* A no-op without a `document`. On the server there is no sheet to batch
|
|
3116
|
+
* against, and `useInsertionEffect` never runs, so nothing would ever close a
|
|
3117
|
+
* window this opened. Skipping it keeps the provider inert during SSR and RSC
|
|
3118
|
+
* instead of merely harmless.
|
|
3119
|
+
*/
|
|
3120
|
+
function openBatchWindow() {
|
|
3121
|
+
if (typeof document === "undefined") return;
|
|
3122
|
+
windowOpen = true;
|
|
3123
|
+
everOpened = true;
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Close a batch window and flush. Called from the provider's
|
|
3127
|
+
* `useInsertionEffect`, i.e. after every render in the commit and before any
|
|
3128
|
+
* layout effect.
|
|
3129
|
+
*/
|
|
3130
|
+
function closeBatchWindow() {
|
|
3131
|
+
windowOpen = false;
|
|
3132
|
+
flushStyles();
|
|
3133
|
+
}
|
|
3134
|
+
/** Whether a batch window is currently open. */
|
|
3135
|
+
function isBatchWindowOpen() {
|
|
3136
|
+
return windowOpen;
|
|
3137
|
+
}
|
|
3138
|
+
function scheduleMicrotaskFlush() {
|
|
3139
|
+
if (microtaskScheduled || flushing) return;
|
|
3140
|
+
microtaskScheduled = true;
|
|
3141
|
+
queueMicrotask(() => {
|
|
3142
|
+
microtaskScheduled = false;
|
|
3143
|
+
windowOpen = false;
|
|
3144
|
+
flushStyles();
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Drain every pending sheet write, in insertion order.
|
|
3149
|
+
*
|
|
3150
|
+
* Safe to call when the queue is empty (the common case, so the guard comes
|
|
3151
|
+
* first) and safe to call re-entrantly: a nested `flushStyles()` is a no-op,
|
|
3152
|
+
* and work a draining write triggers is written in place by
|
|
3153
|
+
* `enqueueStyleWrite` rather than queued behind the rest of the batch.
|
|
3402
3154
|
*/
|
|
3403
|
-
function
|
|
3404
|
-
return
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3155
|
+
function flushStyles() {
|
|
3156
|
+
if (head >= queue.length) return;
|
|
3157
|
+
if (flushing) return;
|
|
3158
|
+
flushing = true;
|
|
3159
|
+
try {
|
|
3160
|
+
while (head < queue.length) {
|
|
3161
|
+
const entry = queue[head++];
|
|
3162
|
+
if (entry.cancelled) {
|
|
3163
|
+
entry.done = true;
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
entry.run();
|
|
3167
|
+
entry.done = true;
|
|
3168
|
+
}
|
|
3169
|
+
} finally {
|
|
3170
|
+
queue = [];
|
|
3171
|
+
head = 0;
|
|
3172
|
+
flushing = false;
|
|
3173
|
+
}
|
|
3409
3174
|
}
|
|
3410
3175
|
/**
|
|
3411
|
-
*
|
|
3412
|
-
*
|
|
3176
|
+
* Dev-only: `batchInjection: true` batches nothing unless a window opens, and no
|
|
3177
|
+
* window has ever opened, so no provider is in the tree. Called by the injector
|
|
3178
|
+
* the first time it declines to batch.
|
|
3413
3179
|
*/
|
|
3414
|
-
function
|
|
3415
|
-
|
|
3180
|
+
function warnBatchProviderMissing() {
|
|
3181
|
+
if (everOpened || warnedNoProvider || !isDevEnv()) return;
|
|
3182
|
+
warnedNoProvider = true;
|
|
3183
|
+
warn("[Tasty] batchInjection needs <TastyBatchProvider> mounted to batch.");
|
|
3416
3184
|
}
|
|
3417
3185
|
/**
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3420
|
-
* `formatRules()` always emits, which makes extraction reliable.
|
|
3186
|
+
* Drop every pending write without applying it. Test helper — production code
|
|
3187
|
+
* should call `flushStyles()` instead.
|
|
3421
3188
|
*/
|
|
3422
|
-
function
|
|
3423
|
-
|
|
3189
|
+
function resetStyleBatch() {
|
|
3190
|
+
queue = [];
|
|
3191
|
+
head = 0;
|
|
3192
|
+
microtaskScheduled = false;
|
|
3193
|
+
flushing = false;
|
|
3194
|
+
windowOpen = false;
|
|
3195
|
+
everOpened = false;
|
|
3196
|
+
warnedNoProvider = false;
|
|
3424
3197
|
}
|
|
3425
3198
|
//#endregion
|
|
3426
3199
|
//#region src/properties/property-type-resolver.ts
|
|
@@ -3432,10 +3205,6 @@ function rscClassRegexGlobal(prefix) {
|
|
|
3432
3205
|
*/
|
|
3433
3206
|
const CUSTOM_PROP_DECL = /^\s*(--[a-z0-9_-]+)\s*:\s*(.+?)\s*$/i;
|
|
3434
3207
|
const SINGLE_VAR_REF = /^var\((--[a-z0-9_-]+)\)$/i;
|
|
3435
|
-
/** A `--name-color-{space}` companion holding a color's channel components. */
|
|
3436
|
-
const COLOR_COMPONENTS_DECL = /^\s*(--[a-z0-9_-]+-color-(?:rgb|hsl|oklch))\s*:\s*(.+?)\s*$/i;
|
|
3437
|
-
/** A component list the browser can type as `<number>+`. */
|
|
3438
|
-
const NUMERIC_COMPONENTS = /^[\d\s.+-]+$/;
|
|
3439
3208
|
var PropertyTypeResolver = class {
|
|
3440
3209
|
/** propName → the prop it depends on */
|
|
3441
3210
|
pendingDeps = /* @__PURE__ */ new Map();
|
|
@@ -3448,15 +3217,6 @@ var PropertyTypeResolver = class {
|
|
|
3448
3217
|
scanDeclarations(declarations, isPropertyDefined, registerProperty) {
|
|
3449
3218
|
if (!declarations.includes("--")) return;
|
|
3450
3219
|
const parts = declarations.split(/;+/);
|
|
3451
|
-
for (const part of parts) {
|
|
3452
|
-
const match = COLOR_COMPONENTS_DECL.exec(part);
|
|
3453
|
-
if (!match) continue;
|
|
3454
|
-
const propName = match[1];
|
|
3455
|
-
if (isPropertyDefined(propName)) continue;
|
|
3456
|
-
if (NUMERIC_COMPONENTS.test(match[2])) continue;
|
|
3457
|
-
if (isPropertyDefined(propName.slice(0, propName.lastIndexOf("-")))) continue;
|
|
3458
|
-
registerProperty(propName, "*", getDefaultComponents());
|
|
3459
|
-
}
|
|
3460
3220
|
for (const part of parts) {
|
|
3461
3221
|
if (!part.trim()) continue;
|
|
3462
3222
|
const match = CUSTOM_PROP_DECL.exec(part);
|
|
@@ -3536,116 +3296,6 @@ const BORDER_STYLES = [
|
|
|
3536
3296
|
"outset"
|
|
3537
3297
|
];
|
|
3538
3298
|
//#endregion
|
|
3539
|
-
//#region src/styles/shared.ts
|
|
3540
|
-
/**
|
|
3541
|
-
* If the group contains exactly one value that is a CSS-wide keyword
|
|
3542
|
-
* and no colors, return the keyword. Otherwise null.
|
|
3543
|
-
*
|
|
3544
|
-
* Direction mods (top/right/bottom/left) may coexist with the keyword
|
|
3545
|
-
* for directional application like `padding="inherit top"`.
|
|
3546
|
-
*/
|
|
3547
|
-
function extractCSSWideKeyword(group) {
|
|
3548
|
-
if (group.values.length !== 1 || group.colors.length > 0) return null;
|
|
3549
|
-
return CSS_WIDE_KEYWORDS.has(group.values[0]) ? group.values[0] : null;
|
|
3550
|
-
}
|
|
3551
|
-
/** A resolved custom-property reference, i.e. `var(--name)` or `var(--name, …)`. */
|
|
3552
|
-
const RE_VAR_REFERENCE = /^var\(/;
|
|
3553
|
-
/**
|
|
3554
|
-
* Assign custom-property references to the style and color slots of a
|
|
3555
|
-
* `<line-width> <line-style> <line-color>` shorthand (`border`, `outline`).
|
|
3556
|
-
*
|
|
3557
|
-
* The parser cannot type a reference, so it buckets `$name` as a plain value and
|
|
3558
|
-
* the handler has to place it. A reference fills the first slot still free, in
|
|
3559
|
-
* shorthand order: the width takes `values[0]`, then the style, then the color.
|
|
3560
|
-
* The style would otherwise arrive as a keyword (`solid`, `dashed`, …) that a
|
|
3561
|
-
* reference has no way to match, and the color as `#name` — the `$name-color`
|
|
3562
|
-
* form the parser buckets as a color exists to reference a raw CSS custom
|
|
3563
|
-
* property, not as the way colors are written, so a reference should not reach
|
|
3564
|
-
* for the color slot until the style slot is taken.
|
|
3565
|
-
*
|
|
3566
|
-
* Lengths are left alone: a second length is not valid in these shorthands, and
|
|
3567
|
-
* promoting one would emit an invalid declaration instead of ignoring an extra
|
|
3568
|
-
* value. Callers supply their own slot defaults.
|
|
3569
|
-
*/
|
|
3570
|
-
function assignLineSlots(values, styleKeyword, colorToken) {
|
|
3571
|
-
if (styleKeyword && colorToken) return {
|
|
3572
|
-
style: styleKeyword,
|
|
3573
|
-
color: colorToken
|
|
3574
|
-
};
|
|
3575
|
-
const spare = values.slice(1).filter((value) => RE_VAR_REFERENCE.test(value) && value !== colorToken);
|
|
3576
|
-
return {
|
|
3577
|
-
style: styleKeyword || spare.shift(),
|
|
3578
|
-
color: colorToken || spare.shift()
|
|
3579
|
-
};
|
|
3580
|
-
}
|
|
3581
|
-
/** Warning keys already emitted, so each distinct offending value warns once. */
|
|
3582
|
-
const emittedWarnings$2 = /* @__PURE__ */ new Set();
|
|
3583
|
-
/** A resolved custom-property reference anywhere in a token. */
|
|
3584
|
-
const RE_HAS_VAR_REFERENCE = /var\(/;
|
|
3585
|
-
/**
|
|
3586
|
-
* Reject a custom-property reference used where a *token name* is expected.
|
|
3587
|
-
*
|
|
3588
|
-
* `preset` and `transition` take the name of a design token (`t3`, `fill`) and
|
|
3589
|
-
* interpolate it into a CSS custom property — `var(--t3-font-size)`. A reference
|
|
3590
|
-
* substituted into that position builds `var(--var(--x)-font-size)`, which is not
|
|
3591
|
-
* a valid custom-property name, so the browser drops the declaration and the
|
|
3592
|
-
* style silently does nothing. The DSL has no way to indirect a token name
|
|
3593
|
-
* through a custom property: the name is needed at build time, and a reference
|
|
3594
|
-
* only resolves in the browser.
|
|
3595
|
-
*
|
|
3596
|
-
* Returns true (warning once per value in dev) so the caller can fall back
|
|
3597
|
-
* instead of emitting a declaration that cannot work.
|
|
3598
|
-
*/
|
|
3599
|
-
function isTokenNameReference(property, name) {
|
|
3600
|
-
if (!RE_HAS_VAR_REFERENCE.test(name)) return false;
|
|
3601
|
-
warnOnceDev(`token-name-reference:${property}:${name}`, `${property}="${name}": a custom property cannot name a token. The name is interpolated into a CSS custom property at build time, so a reference would build an unusable name. It is ignored.`);
|
|
3602
|
-
return true;
|
|
3603
|
-
}
|
|
3604
|
-
/**
|
|
3605
|
-
* Emit a style-level warning at most once per `key`. No-op outside dev mode.
|
|
3606
|
-
*
|
|
3607
|
-
* Style handlers run once per state combination and their results are cached,
|
|
3608
|
-
* but the same value can arrive from many components, so dedupe is required.
|
|
3609
|
-
*
|
|
3610
|
-
* `isDevEnv()` is called lazily rather than captured at module load (the pattern
|
|
3611
|
-
* used in `config.ts` and `states/index.ts`) so that tests can enable it with
|
|
3612
|
-
* `vi.stubEnv('NODE_ENV', 'development')` — `isDevEnv()` returns `false` for
|
|
3613
|
-
* `NODE_ENV=test`, which is why every module-load-gated warning in this repo is
|
|
3614
|
-
* currently untestable. The key is registered *before* the dev check, so
|
|
3615
|
-
* production pays at most one `isDevEnv()` call per distinct offending value and
|
|
3616
|
-
* never touches the console.
|
|
3617
|
-
*/
|
|
3618
|
-
function warnOnceDev(key, message) {
|
|
3619
|
-
if (emittedWarnings$2.has(key)) return;
|
|
3620
|
-
emittedWarnings$2.add(key);
|
|
3621
|
-
if (!isDevEnv()) return;
|
|
3622
|
-
console.warn(`[Tasty] ${message}`);
|
|
3623
|
-
}
|
|
3624
|
-
/** Forget emitted style warnings. Called by `resetConfig()`; test isolation. */
|
|
3625
|
-
function resetStyleWarnings() {
|
|
3626
|
-
emittedWarnings$2.clear();
|
|
3627
|
-
}
|
|
3628
|
-
/**
|
|
3629
|
-
* Report a comma group that names direction modifiers together with more values
|
|
3630
|
-
* than the group can use.
|
|
3631
|
-
*
|
|
3632
|
-
* The parser buckets values and modifiers into separate arrays per comma group,
|
|
3633
|
-
* so `padding: '2x 4x top right'`, `padding: '2x top 4x right'` and
|
|
3634
|
-
* `padding: 'top 2x right 4x'` all reach a handler identically — the pairing a
|
|
3635
|
-
* reader infers from source order does not survive parsing. Such a group
|
|
3636
|
-
* therefore carries a single value, applied to every direction it names.
|
|
3637
|
-
*
|
|
3638
|
-
* `maxValues` is 2 only for a span modifier (`inset: '2x 4x bottom dock'`),
|
|
3639
|
-
* where the second value insets the sides the dock spans.
|
|
3640
|
-
*
|
|
3641
|
-
* Callers must guard on the count themselves so the valid path costs one integer
|
|
3642
|
-
* comparison; everything expensive happens here, on the misuse path only.
|
|
3643
|
-
*/
|
|
3644
|
-
function warnExtraGroupValues(property, input, maxValues) {
|
|
3645
|
-
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".`;
|
|
3646
|
-
warnOnceDev(`extra-values:${property}:${input}`, message);
|
|
3647
|
-
}
|
|
3648
|
-
//#endregion
|
|
3649
3299
|
//#region src/styles/border.ts
|
|
3650
3300
|
/**
|
|
3651
3301
|
* Process a single group and return border values for its directions.
|
|
@@ -3760,21 +3410,18 @@ function borderStyle({ border }) {
|
|
|
3760
3410
|
borderStyle.__lookupStyles = ["border"];
|
|
3761
3411
|
//#endregion
|
|
3762
3412
|
//#region src/styles/color.ts
|
|
3413
|
+
/**
|
|
3414
|
+
* A color that already resolves against the color the element inherits, and so
|
|
3415
|
+
* must not be republished as `--current-color`. Covers both spellings: the
|
|
3416
|
+
* variable (a self-reference) and the keyword (a second resolution).
|
|
3417
|
+
*/
|
|
3418
|
+
const RE_READS_CURRENT = /var\(--current-color[,)]|currentcolor/i;
|
|
3763
3419
|
function colorStyle({ color }) {
|
|
3764
3420
|
if (!color) return null;
|
|
3765
3421
|
if (color === true) color = "currentColor";
|
|
3766
3422
|
color = parseColor(color, true).color || resolveCustomProperties(color);
|
|
3767
|
-
const match = color.match(/var\(--(.+?)-color/);
|
|
3768
|
-
let name = "";
|
|
3769
|
-
if (match) name = match[1];
|
|
3770
3423
|
const styles = { color };
|
|
3771
|
-
if (
|
|
3772
|
-
const suffix = getColorSpaceSuffix();
|
|
3773
|
-
Object.assign(styles, {
|
|
3774
|
-
"--current-color": color,
|
|
3775
|
-
[`--current-color-${suffix}`]: convertColorChainToComponentChain(color)
|
|
3776
|
-
});
|
|
3777
|
-
}
|
|
3424
|
+
if (!RE_READS_CURRENT.test(color)) styles["--current-color"] = color;
|
|
3778
3425
|
return styles;
|
|
3779
3426
|
}
|
|
3780
3427
|
colorStyle.__lookupStyles = ["color"];
|
|
@@ -3804,31 +3451,7 @@ function createStyle(styleName, cssStyle, converter) {
|
|
|
3804
3451
|
}
|
|
3805
3452
|
if (typeof styleValue === "string" && finalCssStyle.startsWith("--") && finalCssStyle.endsWith("-color")) {
|
|
3806
3453
|
styleValue = styleValue.trim();
|
|
3807
|
-
|
|
3808
|
-
const colorSpaceStr = strToColorSpace(styleValue);
|
|
3809
|
-
const { color, name } = parseColor(styleValue);
|
|
3810
|
-
if (name && colorSpaceStr) return {
|
|
3811
|
-
[finalCssStyle]: `var(--${name}-color, ${colorSpaceStr})`,
|
|
3812
|
-
[`${finalCssStyle}-${suffix}`]: `var(--${name}-color-${suffix}, ${getColorSpaceComponents(colorSpaceStr)})`
|
|
3813
|
-
};
|
|
3814
|
-
else if (name) {
|
|
3815
|
-
if (color) return {
|
|
3816
|
-
[finalCssStyle]: color,
|
|
3817
|
-
[`${finalCssStyle}-${suffix}`]: convertColorChainToComponentChain(color)
|
|
3818
|
-
};
|
|
3819
|
-
return {
|
|
3820
|
-
[finalCssStyle]: `var(--${name}-color)`,
|
|
3821
|
-
[`${finalCssStyle}-${suffix}`]: `var(--${name}-color-${suffix})`
|
|
3822
|
-
};
|
|
3823
|
-
} else if (colorSpaceStr) return {
|
|
3824
|
-
[finalCssStyle]: colorSpaceStr,
|
|
3825
|
-
[`${finalCssStyle}-${suffix}`]: getColorSpaceComponents(colorSpaceStr)
|
|
3826
|
-
};
|
|
3827
|
-
if (color && colorFuncName(color)) return {
|
|
3828
|
-
[finalCssStyle]: color,
|
|
3829
|
-
[`${finalCssStyle}-${suffix}`]: convertColorChainToComponentChain(color)
|
|
3830
|
-
};
|
|
3831
|
-
return { [finalCssStyle]: color ?? "" };
|
|
3454
|
+
return { [finalCssStyle]: parseColor(styleValue).color ?? "" };
|
|
3832
3455
|
}
|
|
3833
3456
|
const processed = parseStyle(styleValue);
|
|
3834
3457
|
return { [finalCssStyle]: processed.output };
|
|
@@ -4777,12 +4400,6 @@ scrollMarginStyle.__lookupStyles = [
|
|
|
4777
4400
|
"scrollMarginInline"
|
|
4778
4401
|
];
|
|
4779
4402
|
//#endregion
|
|
4780
|
-
//#region src/utils/warnings.ts
|
|
4781
|
-
const PREFIX = "[Tasty]";
|
|
4782
|
-
function warn(...args) {
|
|
4783
|
-
console.warn(PREFIX, ...args);
|
|
4784
|
-
}
|
|
4785
|
-
//#endregion
|
|
4786
4403
|
//#region src/styles/scrollbar.ts
|
|
4787
4404
|
/**
|
|
4788
4405
|
* Standard CSS scrollbar styling via `scrollbar-width`, `scrollbar-color`,
|
|
@@ -5455,6 +5072,8 @@ var SheetManager = class {
|
|
|
5455
5072
|
propertyTypeResolver: new PropertyTypeResolver(),
|
|
5456
5073
|
usageMap: /* @__PURE__ */ new Map(),
|
|
5457
5074
|
touchCount: 0,
|
|
5075
|
+
touchTick: -1,
|
|
5076
|
+
touchedTick: /* @__PURE__ */ new Set(),
|
|
5458
5077
|
serverClassSyncIndex: 0,
|
|
5459
5078
|
rscStylesScanned: false,
|
|
5460
5079
|
injectionMode: this.detectInjectionMode(root)
|
|
@@ -6365,6 +5984,16 @@ function formatCounterStyleRule(name, descriptors) {
|
|
|
6365
5984
|
//#endregion
|
|
6366
5985
|
//#region src/injector/injector.ts
|
|
6367
5986
|
/**
|
|
5987
|
+
* Placeholder `KeyframesInfo` for a cache entry reserved by a batched write and
|
|
5988
|
+
* not yet backed by a real rule. Only ever read through `entry.pending`, which
|
|
5989
|
+
* is checked before the info is used.
|
|
5990
|
+
*/
|
|
5991
|
+
const EMPTY_KEYFRAMES_INFO = {
|
|
5992
|
+
name: "",
|
|
5993
|
+
ruleIndex: -3,
|
|
5994
|
+
sheetIndex: -3
|
|
5995
|
+
};
|
|
5996
|
+
/**
|
|
6368
5997
|
* Extract class names from `<style data-tasty-rsc>` tags.
|
|
6369
5998
|
* The doubled-specificity pattern `.tXXX.tXXX` makes extraction reliable.
|
|
6370
5999
|
*/
|
|
@@ -6425,6 +6054,56 @@ var StyleInjector = class {
|
|
|
6425
6054
|
get _sheetManager() {
|
|
6426
6055
|
return this.sheetManager;
|
|
6427
6056
|
}
|
|
6057
|
+
/**
|
|
6058
|
+
* Whether sheet writes should be queued instead of applied immediately.
|
|
6059
|
+
*
|
|
6060
|
+
* Only ever true on the client: SSR collects CSS as text and the RSC path
|
|
6061
|
+
* returns it as strings, so neither has a live sheet to batch writes against.
|
|
6062
|
+
*
|
|
6063
|
+
* In the default `true` mode a write is only queued inside an open batch
|
|
6064
|
+
* window — a commit in which `<TastyBatchProvider>` rendered and will
|
|
6065
|
+
* therefore flush in its insertion effect, before any layout effect can
|
|
6066
|
+
* measure. Everything else falls through to a synchronous write, so enabling
|
|
6067
|
+
* the flag cannot make a `useLayoutEffect` read an unstyled element.
|
|
6068
|
+
* `'always'` drops the gate and accepts that trade for wider coverage.
|
|
6069
|
+
*/
|
|
6070
|
+
get batching() {
|
|
6071
|
+
const mode = this.config.batchInjection;
|
|
6072
|
+
if (!mode || typeof document === "undefined") return false;
|
|
6073
|
+
if (mode === "always") return true;
|
|
6074
|
+
if (isBatchWindowOpen()) return true;
|
|
6075
|
+
warnBatchProviderMissing();
|
|
6076
|
+
return false;
|
|
6077
|
+
}
|
|
6078
|
+
/**
|
|
6079
|
+
* Apply a sheet write now, or queue it when batching is on.
|
|
6080
|
+
* Returns the queue handle when deferred, so the caller can cancel it if it
|
|
6081
|
+
* disposes before the flush.
|
|
6082
|
+
*/
|
|
6083
|
+
writeSheet(task) {
|
|
6084
|
+
if (this.batching) return enqueueStyleWrite(task);
|
|
6085
|
+
task();
|
|
6086
|
+
return null;
|
|
6087
|
+
}
|
|
6088
|
+
/**
|
|
6089
|
+
* Insert a global (non-class) rule, honouring batching.
|
|
6090
|
+
*
|
|
6091
|
+
* `onApplied` records the caller's dedupe bookkeeping. When the write is
|
|
6092
|
+
* queued it runs eagerly, so a repeat call before the flush bails out instead
|
|
6093
|
+
* of queueing the same rule twice — the same reasoning as
|
|
6094
|
+
* `insertPropertyRule`'s eager marking. When written synchronously it runs
|
|
6095
|
+
* only on success, preserving the existing retry-on-failure behaviour.
|
|
6096
|
+
*/
|
|
6097
|
+
writeGlobalRule(registry, rules, key, root, onApplied) {
|
|
6098
|
+
if (this.batching) {
|
|
6099
|
+
onApplied?.();
|
|
6100
|
+
enqueueStyleWrite(() => {
|
|
6101
|
+
this.sheetManager.insertGlobalRule(registry, rules, key, root);
|
|
6102
|
+
});
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6105
|
+
if (this.sheetManager.insertGlobalRule(registry, rules, key, root)) onApplied?.();
|
|
6106
|
+
}
|
|
6428
6107
|
constructor(config = {}) {
|
|
6429
6108
|
if (config.namePrefix !== void 0) validateNamePrefix(config.namePrefix);
|
|
6430
6109
|
this.config = config;
|
|
@@ -6502,12 +6181,18 @@ var StyleInjector = class {
|
|
|
6502
6181
|
};
|
|
6503
6182
|
const cacheKey = options?.cacheKey;
|
|
6504
6183
|
let className;
|
|
6505
|
-
let isPreAllocated = false;
|
|
6506
6184
|
if (cacheKey && registry.cacheKeyToClassName.has(cacheKey)) {
|
|
6507
6185
|
className = registry.cacheKeyToClassName.get(cacheKey);
|
|
6508
6186
|
const existingRuleInfo = registry.rules.get(className);
|
|
6509
|
-
|
|
6510
|
-
|
|
6187
|
+
if (existingRuleInfo.ruleIndex === -3) {
|
|
6188
|
+
registry.refCounts.set(className, (registry.refCounts.get(className) || 0) + 1);
|
|
6189
|
+
if (registry.metrics) registry.metrics.hits++;
|
|
6190
|
+
return {
|
|
6191
|
+
className,
|
|
6192
|
+
dispose: () => this.dispose(className, registry)
|
|
6193
|
+
};
|
|
6194
|
+
}
|
|
6195
|
+
if (!(existingRuleInfo.ruleIndex === -1 && existingRuleInfo.sheetIndex === -1)) {
|
|
6511
6196
|
const currentRefCount = registry.refCounts.get(className) || 0;
|
|
6512
6197
|
registry.refCounts.set(className, currentRefCount + 1);
|
|
6513
6198
|
if (registry.metrics) registry.metrics.hits++;
|
|
@@ -6548,39 +6233,70 @@ var StyleInjector = class {
|
|
|
6548
6233
|
rootPrefix: void 0
|
|
6549
6234
|
};
|
|
6550
6235
|
});
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6236
|
+
const applySheetWrite = () => {
|
|
6237
|
+
if (this.config.autoPropertyTypes !== false) {
|
|
6238
|
+
const resolver = registry.propertyTypeResolver;
|
|
6239
|
+
const defined = registry.injectedProperties;
|
|
6240
|
+
for (const rule of rulesToInsert) {
|
|
6241
|
+
if (!rule.declarations) continue;
|
|
6242
|
+
resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
|
|
6243
|
+
this.property(name, {
|
|
6244
|
+
syntax,
|
|
6245
|
+
inherits: true,
|
|
6246
|
+
initialValue,
|
|
6247
|
+
root
|
|
6248
|
+
});
|
|
6562
6249
|
});
|
|
6563
|
-
}
|
|
6250
|
+
}
|
|
6564
6251
|
}
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6252
|
+
const ruleInfo = this.sheetManager.insertRule(registry, rulesToInsert, className, root);
|
|
6253
|
+
if (!ruleInfo) {
|
|
6254
|
+
if (registry.metrics) registry.metrics.misses++;
|
|
6255
|
+
return null;
|
|
6256
|
+
}
|
|
6257
|
+
registry.rules.set(className, ruleInfo);
|
|
6258
|
+
if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
|
|
6259
|
+
if (registry.metrics) {
|
|
6260
|
+
registry.metrics.totalInsertions++;
|
|
6261
|
+
registry.metrics.misses++;
|
|
6262
|
+
}
|
|
6263
|
+
return ruleInfo;
|
|
6264
|
+
};
|
|
6265
|
+
if (this.batching) {
|
|
6266
|
+
registry.rules.set(className, {
|
|
6267
|
+
className,
|
|
6268
|
+
ruleIndex: -3,
|
|
6269
|
+
sheetIndex: -3
|
|
6270
|
+
});
|
|
6271
|
+
if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
|
|
6272
|
+
registry.refCounts.set(className, 1);
|
|
6273
|
+
const queued = enqueueStyleWrite(() => {
|
|
6274
|
+
if (applySheetWrite()) return;
|
|
6275
|
+
registry.rules.set(className, {
|
|
6276
|
+
className,
|
|
6277
|
+
ruleIndex: -1,
|
|
6278
|
+
sheetIndex: -1
|
|
6279
|
+
});
|
|
6280
|
+
});
|
|
6569
6281
|
return {
|
|
6570
6282
|
className,
|
|
6571
|
-
dispose: () => {
|
|
6283
|
+
dispose: () => {
|
|
6284
|
+
if (!queued.done && registry.refCounts.get(className) === 1 && registry.rules.get(className)?.ruleIndex === -3) {
|
|
6285
|
+
queued.cancelled = true;
|
|
6286
|
+
registry.rules.delete(className);
|
|
6287
|
+
if (cacheKey) registry.cacheKeyToClassName.delete(cacheKey);
|
|
6288
|
+
registry.refCounts.set(className, 0);
|
|
6289
|
+
return;
|
|
6290
|
+
}
|
|
6291
|
+
this.dispose(className, registry);
|
|
6292
|
+
}
|
|
6572
6293
|
};
|
|
6573
6294
|
}
|
|
6295
|
+
if (!applySheetWrite()) return {
|
|
6296
|
+
className,
|
|
6297
|
+
dispose: () => {}
|
|
6298
|
+
};
|
|
6574
6299
|
registry.refCounts.set(className, 1);
|
|
6575
|
-
if (isPreAllocated) registry.rules.set(className, ruleInfo);
|
|
6576
|
-
else {
|
|
6577
|
-
registry.rules.set(className, ruleInfo);
|
|
6578
|
-
if (cacheKey) registry.cacheKeyToClassName.set(cacheKey, className);
|
|
6579
|
-
}
|
|
6580
|
-
if (registry.metrics) {
|
|
6581
|
-
registry.metrics.totalInsertions++;
|
|
6582
|
-
registry.metrics.misses++;
|
|
6583
|
-
}
|
|
6584
6300
|
return {
|
|
6585
6301
|
className,
|
|
6586
6302
|
dispose: () => this.dispose(className, registry)
|
|
@@ -6595,24 +6311,41 @@ var StyleInjector = class {
|
|
|
6595
6311
|
const root = options?.root || document;
|
|
6596
6312
|
const registry = this.sheetManager.getRegistry(root);
|
|
6597
6313
|
if (!rules || rules.length === 0) return { dispose: () => {} };
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6314
|
+
const key = `global:${this.globalRuleCounter++}`;
|
|
6315
|
+
const applyWrite = () => {
|
|
6316
|
+
if (this.config.autoPropertyTypes !== false) {
|
|
6317
|
+
const resolver = registry.propertyTypeResolver;
|
|
6318
|
+
const defined = registry.injectedProperties;
|
|
6319
|
+
for (const rule of rules) {
|
|
6320
|
+
if (!rule.declarations) continue;
|
|
6321
|
+
resolver.scanDeclarations(rule.declarations, (name) => defined.has(name), (name, syntax, initialValue) => {
|
|
6322
|
+
this.property(name, {
|
|
6323
|
+
syntax,
|
|
6324
|
+
inherits: true,
|
|
6325
|
+
initialValue,
|
|
6326
|
+
root
|
|
6327
|
+
});
|
|
6609
6328
|
});
|
|
6610
|
-
}
|
|
6329
|
+
}
|
|
6611
6330
|
}
|
|
6331
|
+
const inserted = this.sheetManager.insertGlobalRule(registry, rules, key, root);
|
|
6332
|
+
if (registry.metrics) registry.metrics.totalInsertions++;
|
|
6333
|
+
return inserted;
|
|
6334
|
+
};
|
|
6335
|
+
if (this.batching) {
|
|
6336
|
+
let info = null;
|
|
6337
|
+
const queued = enqueueStyleWrite(() => {
|
|
6338
|
+
info = applyWrite();
|
|
6339
|
+
});
|
|
6340
|
+
return { dispose: () => {
|
|
6341
|
+
if (!queued.done) {
|
|
6342
|
+
queued.cancelled = true;
|
|
6343
|
+
return;
|
|
6344
|
+
}
|
|
6345
|
+
if (info) this.sheetManager.deleteGlobalRule(registry, key);
|
|
6346
|
+
} };
|
|
6612
6347
|
}
|
|
6613
|
-
const
|
|
6614
|
-
const info = this.sheetManager.insertGlobalRule(registry, rules, key, root);
|
|
6615
|
-
if (registry.metrics) registry.metrics.totalInsertions++;
|
|
6348
|
+
const info = applyWrite();
|
|
6616
6349
|
return { dispose: () => {
|
|
6617
6350
|
if (info) this.sheetManager.deleteGlobalRule(registry, key);
|
|
6618
6351
|
} };
|
|
@@ -6624,12 +6357,24 @@ var StyleInjector = class {
|
|
|
6624
6357
|
*/
|
|
6625
6358
|
injectRawCSS(css, options) {
|
|
6626
6359
|
const root = options?.root || document;
|
|
6627
|
-
return this.sheetManager.injectRawCSS(css, root);
|
|
6360
|
+
if (!this.batching) return this.sheetManager.injectRawCSS(css, root);
|
|
6361
|
+
let result = null;
|
|
6362
|
+
const queued = enqueueStyleWrite(() => {
|
|
6363
|
+
result = this.sheetManager.injectRawCSS(css, root);
|
|
6364
|
+
});
|
|
6365
|
+
return { dispose: () => {
|
|
6366
|
+
if (!queued.done) {
|
|
6367
|
+
queued.cancelled = true;
|
|
6368
|
+
return;
|
|
6369
|
+
}
|
|
6370
|
+
result?.dispose();
|
|
6371
|
+
} };
|
|
6628
6372
|
}
|
|
6629
6373
|
/**
|
|
6630
6374
|
* Get raw CSS text for SSR
|
|
6631
6375
|
*/
|
|
6632
6376
|
getRawCSSText(options) {
|
|
6377
|
+
flushStyles();
|
|
6633
6378
|
const root = options?.root || document;
|
|
6634
6379
|
return this.sheetManager.getRawCSSText(root);
|
|
6635
6380
|
}
|
|
@@ -6666,6 +6411,7 @@ var StyleInjector = class {
|
|
|
6666
6411
|
* Force bulk cleanup of unused styles
|
|
6667
6412
|
*/
|
|
6668
6413
|
cleanup(root) {
|
|
6414
|
+
flushStyles();
|
|
6669
6415
|
const registry = this.sheetManager.getRegistry(root || document);
|
|
6670
6416
|
this.sheetManager.forceCleanup(registry);
|
|
6671
6417
|
}
|
|
@@ -6673,6 +6419,7 @@ var StyleInjector = class {
|
|
|
6673
6419
|
* Get CSS text from all sheets (for SSR)
|
|
6674
6420
|
*/
|
|
6675
6421
|
getCSSText(options) {
|
|
6422
|
+
flushStyles();
|
|
6676
6423
|
const root = options?.root || document;
|
|
6677
6424
|
const registry = this.sheetManager.getRegistry(root);
|
|
6678
6425
|
return this.sheetManager.getCSSText(registry);
|
|
@@ -6681,6 +6428,7 @@ var StyleInjector = class {
|
|
|
6681
6428
|
* Get CSS only for the provided tasty classNames (e.g., ["t0","t3"])
|
|
6682
6429
|
*/
|
|
6683
6430
|
getCSSTextForClasses(classNames, options) {
|
|
6431
|
+
flushStyles();
|
|
6684
6432
|
const root = options?.root || document;
|
|
6685
6433
|
const registry = this.sheetManager.getRegistry(root);
|
|
6686
6434
|
const cssChunks = [];
|
|
@@ -6705,6 +6453,7 @@ var StyleInjector = class {
|
|
|
6705
6453
|
* Get cache performance metrics
|
|
6706
6454
|
*/
|
|
6707
6455
|
getMetrics(options) {
|
|
6456
|
+
flushStyles();
|
|
6708
6457
|
const root = options?.root || document;
|
|
6709
6458
|
const registry = this.sheetManager.getRegistry(root);
|
|
6710
6459
|
return this.sheetManager.getMetrics(registry);
|
|
@@ -6748,15 +6497,6 @@ var StyleInjector = class {
|
|
|
6748
6497
|
const cssName = effectiveResult.cssName;
|
|
6749
6498
|
const definition = effectiveResult.definition;
|
|
6750
6499
|
this.insertPropertyRule(registry, root, cssName, definition, name);
|
|
6751
|
-
if (effectiveResult.isColor) {
|
|
6752
|
-
const companionCssName = `${cssName}-${getColorSpaceSuffix()}`;
|
|
6753
|
-
const companionDefinition = {
|
|
6754
|
-
syntax: getComponentPropertySyntax(),
|
|
6755
|
-
inherits: definition.inherits,
|
|
6756
|
-
initialValue: colorInitialValueToComponents(definition.initialValue)
|
|
6757
|
-
};
|
|
6758
|
-
this.insertPropertyRule(registry, root, companionCssName, companionDefinition, `${name}:components`);
|
|
6759
|
-
}
|
|
6760
6500
|
}
|
|
6761
6501
|
/**
|
|
6762
6502
|
* Build and insert a single `@property` rule into the given registry.
|
|
@@ -6784,7 +6524,7 @@ var StyleInjector = class {
|
|
|
6784
6524
|
declarations
|
|
6785
6525
|
};
|
|
6786
6526
|
registry.injectedProperties.set(cssName, normalizePropertyDefinition(definition));
|
|
6787
|
-
this.
|
|
6527
|
+
this.writeGlobalRule(registry, [rule], `property:${cacheKey}`, root);
|
|
6788
6528
|
}
|
|
6789
6529
|
/**
|
|
6790
6530
|
* Check whether a given @property name was already injected by this injector.
|
|
@@ -6795,6 +6535,7 @@ var StyleInjector = class {
|
|
|
6795
6535
|
* - `--name` → checks `--name` (legacy format)
|
|
6796
6536
|
*/
|
|
6797
6537
|
isPropertyDefined(name, options) {
|
|
6538
|
+
flushStyles();
|
|
6798
6539
|
const root = options?.root || document;
|
|
6799
6540
|
const registry = this.sheetManager.getRegistry(root);
|
|
6800
6541
|
const effectiveResult = getEffectiveDefinition(name, {});
|
|
@@ -6816,7 +6557,9 @@ var StyleInjector = class {
|
|
|
6816
6557
|
selector: "@font-face",
|
|
6817
6558
|
declarations: formatFontFaceDeclarations(family, descriptors)
|
|
6818
6559
|
};
|
|
6819
|
-
|
|
6560
|
+
this.writeGlobalRule(registry, [rule], `fontface:${hash}`, root, () => {
|
|
6561
|
+
registry.injectedFontFaces.add(hash);
|
|
6562
|
+
});
|
|
6820
6563
|
}
|
|
6821
6564
|
/**
|
|
6822
6565
|
* Inject a CSS @counter-style rule.
|
|
@@ -6834,13 +6577,17 @@ var StyleInjector = class {
|
|
|
6834
6577
|
const existingIsStrong = registry.injectedCounterStyles.get(name);
|
|
6835
6578
|
if (existingIsStrong !== void 0) {
|
|
6836
6579
|
if (isWeak || existingIsStrong === true) return;
|
|
6837
|
-
this.
|
|
6580
|
+
this.writeSheet(() => {
|
|
6581
|
+
this.sheetManager.deleteGlobalRule(registry, `counterstyle:${name}`);
|
|
6582
|
+
});
|
|
6838
6583
|
}
|
|
6839
6584
|
const rule = {
|
|
6840
6585
|
selector: `@counter-style ${name}`,
|
|
6841
6586
|
declarations: formatCounterStyleDeclarations(descriptors)
|
|
6842
6587
|
};
|
|
6843
|
-
|
|
6588
|
+
this.writeGlobalRule(registry, [rule], `counterstyle:${name}`, root, () => {
|
|
6589
|
+
registry.injectedCounterStyles.set(name, !isWeak);
|
|
6590
|
+
});
|
|
6844
6591
|
}
|
|
6845
6592
|
/**
|
|
6846
6593
|
* Inject a CSS @function rule (custom function).
|
|
@@ -6859,13 +6606,17 @@ var StyleInjector = class {
|
|
|
6859
6606
|
const existingIsStrong = registry.injectedFunctions.get(cssName);
|
|
6860
6607
|
if (existingIsStrong !== void 0) {
|
|
6861
6608
|
if (isWeak || existingIsStrong === true) return;
|
|
6862
|
-
this.
|
|
6609
|
+
this.writeSheet(() => {
|
|
6610
|
+
this.sheetManager.deleteGlobalRule(registry, `function:${cssName}`);
|
|
6611
|
+
});
|
|
6863
6612
|
}
|
|
6864
6613
|
const rule = {
|
|
6865
6614
|
selector: formatFunctionPrelude(name, definition.args, definition.returns),
|
|
6866
6615
|
declarations: formatFunctionDeclarations(definition)
|
|
6867
6616
|
};
|
|
6868
|
-
|
|
6617
|
+
this.writeGlobalRule(registry, [rule], `function:${cssName}`, root, () => {
|
|
6618
|
+
registry.injectedFunctions.set(cssName, !isWeak);
|
|
6619
|
+
});
|
|
6869
6620
|
}
|
|
6870
6621
|
/**
|
|
6871
6622
|
* Inject keyframes and return object with toString() and dispose()
|
|
@@ -6903,29 +6654,53 @@ var StyleInjector = class {
|
|
|
6903
6654
|
registry.keyframesNameToContent.set(providedName, contentHash);
|
|
6904
6655
|
}
|
|
6905
6656
|
} else actualName = makeKeyframeName(this.namePrefix, String(registry.keyframesCounter++));
|
|
6906
|
-
const
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6657
|
+
const applyWrite = () => {
|
|
6658
|
+
const result = this.sheetManager.insertKeyframes(registry, steps, actualName, root);
|
|
6659
|
+
if (!result) return false;
|
|
6660
|
+
const { info, declarations } = result;
|
|
6661
|
+
if (this.config.autoPropertyTypes !== false && declarations) registry.propertyTypeResolver.scanDeclarations(declarations, (name) => registry.injectedProperties.has(name), (name, syntax, initialValue) => {
|
|
6662
|
+
this.property(name, {
|
|
6663
|
+
syntax,
|
|
6664
|
+
inherits: true,
|
|
6665
|
+
initialValue,
|
|
6666
|
+
root
|
|
6667
|
+
});
|
|
6668
|
+
});
|
|
6669
|
+
const entry = registry.keyframesCache.get(contentHash);
|
|
6670
|
+
if (entry) {
|
|
6671
|
+
entry.info = info;
|
|
6672
|
+
entry.pending = void 0;
|
|
6673
|
+
} else registry.keyframesCache.set(contentHash, {
|
|
6674
|
+
name: actualName,
|
|
6675
|
+
refCount: 1,
|
|
6676
|
+
info
|
|
6677
|
+
});
|
|
6678
|
+
if (registry.metrics) {
|
|
6679
|
+
registry.metrics.totalInsertions++;
|
|
6680
|
+
registry.metrics.misses++;
|
|
6681
|
+
}
|
|
6682
|
+
return true;
|
|
6910
6683
|
};
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6684
|
+
if (this.batching) {
|
|
6685
|
+
const entry = {
|
|
6686
|
+
name: actualName,
|
|
6687
|
+
refCount: 1,
|
|
6688
|
+
info: EMPTY_KEYFRAMES_INFO
|
|
6689
|
+
};
|
|
6690
|
+
registry.keyframesCache.set(contentHash, entry);
|
|
6691
|
+
entry.pending = enqueueStyleWrite(() => {
|
|
6692
|
+
if (applyWrite()) return;
|
|
6693
|
+
if (registry.keyframesCache.get(contentHash) === entry) registry.keyframesCache.delete(contentHash);
|
|
6918
6694
|
});
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
info
|
|
6924
|
-
});
|
|
6925
|
-
if (registry.metrics) {
|
|
6926
|
-
registry.metrics.totalInsertions++;
|
|
6927
|
-
registry.metrics.misses++;
|
|
6695
|
+
return {
|
|
6696
|
+
toString: () => actualName,
|
|
6697
|
+
dispose: () => this.disposeKeyframes(contentHash, registry)
|
|
6698
|
+
};
|
|
6928
6699
|
}
|
|
6700
|
+
if (!applyWrite()) return {
|
|
6701
|
+
toString: () => "",
|
|
6702
|
+
dispose: () => {}
|
|
6703
|
+
};
|
|
6929
6704
|
return {
|
|
6930
6705
|
toString: () => actualName,
|
|
6931
6706
|
dispose: () => this.disposeKeyframes(contentHash, registry)
|
|
@@ -6939,7 +6714,8 @@ var StyleInjector = class {
|
|
|
6939
6714
|
if (!entry) return;
|
|
6940
6715
|
entry.refCount--;
|
|
6941
6716
|
if (entry.refCount <= 0) {
|
|
6942
|
-
|
|
6717
|
+
if (entry.pending && !entry.pending.done) entry.pending.cancelled = true;
|
|
6718
|
+
else this.sheetManager.deleteKeyframes(registry, entry.info);
|
|
6943
6719
|
registry.keyframesCache.delete(contentHash);
|
|
6944
6720
|
for (const [name, hash] of registry.keyframesNameToContent.entries()) if (hash === contentHash) {
|
|
6945
6721
|
registry.keyframesNameToContent.delete(name);
|
|
@@ -6964,6 +6740,11 @@ var StyleInjector = class {
|
|
|
6964
6740
|
const root = options?.root || document;
|
|
6965
6741
|
const registry = this.sheetManager.getRegistry(root);
|
|
6966
6742
|
const now = Date.now();
|
|
6743
|
+
if (registry.touchTick !== now) {
|
|
6744
|
+
registry.touchTick = now;
|
|
6745
|
+
registry.touchedTick.clear();
|
|
6746
|
+
} else if (registry.touchedTick.has(className)) return;
|
|
6747
|
+
registry.touchedTick.add(className);
|
|
6967
6748
|
const parts = className.indexOf(" ") === -1 ? [className] : className.split(" ");
|
|
6968
6749
|
for (const cls of parts) {
|
|
6969
6750
|
if (!this.classRegex.test(cls)) continue;
|
|
@@ -7004,6 +6785,7 @@ var StyleInjector = class {
|
|
|
7004
6785
|
* @returns Number of styles evicted.
|
|
7005
6786
|
*/
|
|
7006
6787
|
gc(options) {
|
|
6788
|
+
flushStyles();
|
|
7007
6789
|
if (typeof document === "undefined") return 0;
|
|
7008
6790
|
if (this.pendingGCHandle != null) {
|
|
7009
6791
|
if (typeof cancelIdleCallback !== "undefined") cancelIdleCallback(this.pendingGCHandle);
|
|
@@ -7053,6 +6835,7 @@ var StyleInjector = class {
|
|
|
7053
6835
|
* Destroy all resources for a root
|
|
7054
6836
|
*/
|
|
7055
6837
|
destroy(root) {
|
|
6838
|
+
flushStyles();
|
|
7056
6839
|
const targetRoot = root || document;
|
|
7057
6840
|
this.sheetManager.cleanup(targetRoot);
|
|
7058
6841
|
if (this.pendingGCHandle != null && !this.sheetManager.hasActiveRoots()) {
|
|
@@ -11828,7 +11611,7 @@ const DEFAULT_PROPERTIES = {
|
|
|
11828
11611
|
},
|
|
11829
11612
|
"#current": {
|
|
11830
11613
|
inherits: true,
|
|
11831
|
-
initialValue: "
|
|
11614
|
+
initialValue: "currentcolor"
|
|
11832
11615
|
},
|
|
11833
11616
|
"#white": {
|
|
11834
11617
|
inherits: true,
|
|
@@ -12378,10 +12161,7 @@ function configure(config = {}) {
|
|
|
12378
12161
|
const tokenKeys = new Set(Object.keys(mergedConfigTokens));
|
|
12379
12162
|
for (const key of Object.keys(mergedReplaceTokens)) if (tokenKeys.has(key)) warnOnce(`token-conflict-${key}`, `[Tasty] Token "${key}" is defined in both \`tokens\` and \`replaceTokens\`. \`replaceTokens\` performs parse-time substitution, so the \`tokens\` CSS custom property will be injected but never used by Tasty styles. Remove it from one of the two.`);
|
|
12380
12163
|
}
|
|
12381
|
-
if (config.colorSpace) {
|
|
12382
|
-
setColorSpace(config.colorSpace);
|
|
12383
|
-
getGlobalParser().clearCache();
|
|
12384
|
-
}
|
|
12164
|
+
if (config.colorSpace) warnOnce("colorSpace-deprecated", "[Tasty] configure({ colorSpace }) no longer has any effect and will be removed in the next major. Colors are emitted as authored, and opacity is applied with relative color syntax, which reads a color's channels in the browser. To address channels yourself, write `oklch(from var(--name-color) l c h)` against the token.");
|
|
12385
12165
|
if (Object.keys(mergedStates).length > 0) setGlobalPredefinedStates(mergedStates);
|
|
12386
12166
|
const parser = getGlobalParser();
|
|
12387
12167
|
if (config.parserCacheSize !== void 0) parser.updateOptions({ cacheSize: config.parserCacheSize });
|
|
@@ -12493,7 +12273,6 @@ function resetConfig() {
|
|
|
12493
12273
|
resetStyleChunks();
|
|
12494
12274
|
resetPropHandlers();
|
|
12495
12275
|
resetBaseStyleProps();
|
|
12496
|
-
resetColorSpace();
|
|
12497
12276
|
clearPipelineCache();
|
|
12498
12277
|
emittedWarnings.clear();
|
|
12499
12278
|
resetStyleWarnings();
|
|
@@ -12501,6 +12280,6 @@ function resetConfig() {
|
|
|
12501
12280
|
delete storage[GLOBAL_INJECTOR_KEY];
|
|
12502
12281
|
}
|
|
12503
12282
|
//#endregion
|
|
12504
|
-
export { SheetManager as $,
|
|
12283
|
+
export { SheetManager as $, StyleParser as $t, FLOW_STYLES as A, STYLE_TO_CHUNK as At, createStateParserContext as B, getGlobalParseFunctions 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, parseStyle as Gt, extractPredefinedStateRefs as H, getGlobalPredefinedTokens as Ht, isSelector as I, registerLocalFunctionPolyfills as It, hasLocalCounterStyle as J, okhstPlugin as Jt, extractLocalCounterStyle as K, stringifyStyles 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, isDevEnv 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, normalizeColorTokenValue as Ut, extractLocalPredefinedStates as V, getGlobalParser as Vt, setGlobalPredefinedStates as W, parseColor as Wt, fontFaceContentHash as X, okhslPlugin as Xt, extractLocalFontFace as Y, okhslFunction as Yt, formatFontFaceRule as Z, createColorFunc as Zt, isFunctionsPolyfillEnabled as _, hashString as _t, getGlobalCounterStyles as a, hslToRgbValues as an, closeBatchWindow as at, resetConfig as b, hasLocalProperties as bt, getGlobalInjector as c, openBatchWindow as ct, getGlobalStyles as d, DEFAULT_ZERO_NAME_PREFIX as dt, Lru as en, STYLE_HANDLER_MAP as et, getNamePrefix as f, makeClassName as ft, isConfigLocked as g, validateNamePrefix as gt, hasStylesGenerated as h, tastyClassRegex as ht, getGlobalConfigTokens as i, hexToRgb as in, PropertyTypeResolver as it, INNER_STYLES as j, extractLocalFunctions as jt, DIMENSION_STYLES as k, POSITION_CHUNK_STYLES as kt, getGlobalKeyframes as l, resetStyleBatch as lt, hasGlobalRecipes as m, makeKeyframeName as mt, getConfig as n, getNamedColorHex as nn, styleHandlers as nt, getGlobalFontFaces as o, strToRgb as on, flushStyles as ot, hasGlobalKeyframes as p, makeCounterStyleName as pt, formatCounterStyleRule as q, okhstFunction as qt, getEffectiveProperties as r, getRgbValuesFromRgbaString as rn, createStyle as rt, getGlobalFunctions as s, normalizeDslName as sn, hasPendingStyleWrites as st, configure as t, overrideColorAlpha as tn, defineHandler as tt, getGlobalRecipes as u, 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 };
|
|
12505
12284
|
|
|
12506
|
-
//# sourceMappingURL=config-
|
|
12285
|
+
//# sourceMappingURL=config-zkrtb424.js.map
|