@kbach/react 0.6.25 → 0.6.26

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.
@@ -580,6 +580,47 @@ function kbachWarn(message) {
580
580
  );
581
581
  }
582
582
 
583
+ // src/core/cache.ts
584
+ var LRUCache = class {
585
+ constructor(capacity = 1e4, onEvict) {
586
+ this.capacity = capacity;
587
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
588
+ this.cache = /* @__PURE__ */ new Map();
589
+ this.onEvict = onEvict;
590
+ }
591
+ get(key) {
592
+ if (!this.cache.has(key)) return void 0;
593
+ const value = this.cache.get(key);
594
+ this.cache.delete(key);
595
+ this.cache.set(key, value);
596
+ return value;
597
+ }
598
+ set(key, value) {
599
+ if (this.cache.has(key)) {
600
+ this.cache.delete(key);
601
+ } else if (this.cache.size >= this.capacity) {
602
+ const oldestKey = this.cache.keys().next().value;
603
+ const oldestValue = this.cache.get(oldestKey);
604
+ this.cache.delete(oldestKey);
605
+ this.onEvict?.(oldestKey, oldestValue);
606
+ }
607
+ this.cache.set(key, value);
608
+ return this;
609
+ }
610
+ has(key) {
611
+ return this.cache.has(key);
612
+ }
613
+ delete(key) {
614
+ return this.cache.delete(key);
615
+ }
616
+ clear() {
617
+ this.cache.clear();
618
+ }
619
+ get size() {
620
+ return this.cache.size;
621
+ }
622
+ };
623
+
583
624
  // src/core/registry.ts
584
625
  var BUILTIN_MODIFIERS = {
585
626
  // ── Mode ─────────────────────────────────────────────────────────────────
@@ -666,7 +707,7 @@ function clearPluginModifiers() {
666
707
  _invalidate();
667
708
  }
668
709
  var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
669
- var _namedModifierCache = /* @__PURE__ */ new Map();
710
+ var _namedModifierCache = new LRUCache(1e4);
670
711
  function getNamedGroupPeerModifier(name) {
671
712
  const cached = _namedModifierCache.get(name);
672
713
  if (cached) return cached;
@@ -715,14 +756,16 @@ function matchModifier(name, isDark, state, breakpoints) {
715
756
  return getModifier(name)?.jsMatch?.(isDark, state, breakpoints) ?? false;
716
757
  }
717
758
 
718
- // src/core/utilities.ts
759
+ // src/core/resolvers/color.ts
719
760
  function resolveColor(value, colors, isArbitrary) {
720
761
  if (isArbitrary) return value;
721
762
  const slashIdx = value.indexOf("/");
722
763
  const colorPart = slashIdx > 0 ? value.slice(0, slashIdx) : value;
723
764
  const opacityPart = slashIdx > 0 ? value.slice(slashIdx + 1) : null;
724
765
  let hex = null;
725
- if (colorPart in colors) {
766
+ if (colorPart.startsWith("[") && colorPart.endsWith("]")) {
767
+ hex = colorPart.slice(1, -1);
768
+ } else if (colorPart in colors) {
726
769
  const entry = colors[colorPart];
727
770
  if (typeof entry === "string") hex = entry;
728
771
  else if (typeof entry === "object" && "6" in entry) hex = entry["6"];
@@ -787,6 +830,86 @@ function withOpacityVar(color, varName) {
787
830
  const [r, g, b] = rgb;
788
831
  return `rgba(${r},${g},${b},var(${varName},1))`;
789
832
  }
833
+ var colorResolvers = {
834
+ // ── Background ─────────────────────────────────────────────────────────────
835
+ bg: ({ value, isArbitrary }, { colors }) => {
836
+ const color = resolveColor(value, colors, isArbitrary);
837
+ if (!color) return null;
838
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
839
+ },
840
+ "bg-opacity": ({ value, isArbitrary }, _) => {
841
+ if (!getEffectiveIsWeb()) return null;
842
+ const n = parseFloat(value);
843
+ if (isNaN(n)) return null;
844
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
845
+ return { "--bg-opacity": v };
846
+ },
847
+ // ── Background gradient (CSS variable gradient stops, web-only) ──────────
848
+ "bg-gradient-to": ({ value }) => {
849
+ if (!getEffectiveIsWeb()) return null;
850
+ const directions = {
851
+ t: "to top",
852
+ tr: "to top right",
853
+ r: "to right",
854
+ br: "to bottom right",
855
+ b: "to bottom",
856
+ bl: "to bottom left",
857
+ l: "to left",
858
+ tl: "to top left"
859
+ };
860
+ const dir = directions[value];
861
+ if (!dir) return null;
862
+ return {
863
+ backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
864
+ };
865
+ },
866
+ from: ({ value, isArbitrary }, { colors }) => {
867
+ if (!getEffectiveIsWeb()) return null;
868
+ const color = resolveColor(value, colors, isArbitrary);
869
+ if (!color) return null;
870
+ return {
871
+ "--kb-gradient-from": color,
872
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
873
+ };
874
+ },
875
+ via: ({ value, isArbitrary }, { colors }) => {
876
+ if (!getEffectiveIsWeb()) return null;
877
+ const color = resolveColor(value, colors, isArbitrary);
878
+ if (!color) return null;
879
+ return {
880
+ "--kb-gradient-via": color,
881
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
882
+ };
883
+ },
884
+ to: ({ value, isArbitrary }, { colors }) => {
885
+ if (!getEffectiveIsWeb()) return null;
886
+ const color = resolveColor(value, colors, isArbitrary);
887
+ if (!color) return null;
888
+ return { "--kb-gradient-to": color };
889
+ },
890
+ // ── Tint color (native-only, for Image and icon components) ──────────────
891
+ tint: ({ value, isArbitrary }, { colors }) => {
892
+ if (getEffectiveIsWeb()) return null;
893
+ const color = resolveColor(value, colors, isArbitrary);
894
+ return color ? { tintColor: color } : null;
895
+ },
896
+ // ── Caret color (web-only) ────────────────────────────────────────────────
897
+ caret: ({ value, isArbitrary }, { colors }) => {
898
+ if (!getEffectiveIsWeb()) return null;
899
+ if (value === "auto" || value === "transparent") return { caretColor: value };
900
+ const color = resolveColor(value, colors, isArbitrary);
901
+ return color ? { caretColor: color } : null;
902
+ },
903
+ // ── Accent color (web-only) ───────────────────────────────────────────────
904
+ accent: ({ value, isArbitrary }, { colors }) => {
905
+ if (!getEffectiveIsWeb()) return null;
906
+ if (value === "auto") return { accentColor: "auto" };
907
+ const color = resolveColor(value, colors, isArbitrary);
908
+ return color ? { accentColor: color } : null;
909
+ }
910
+ };
911
+
912
+ // src/core/resolvers/spacing.ts
790
913
  function resolveSpacing(value, negative, spacing, isArbitrary) {
791
914
  if (isArbitrary) {
792
915
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -819,379 +942,11 @@ function resolveSizing(value, spacing, isArbitrary) {
819
942
  }
820
943
  return null;
821
944
  }
822
- function resolveRadius(value, radii, isArbitrary) {
823
- if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
824
- const key = value === "" ? "DEFAULT" : value;
825
- return radii[key] ?? null;
826
- }
827
- function resolveFontSize(value, fontSizes, isArbitrary) {
828
- if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
829
- return fontSizes[value] ?? null;
830
- }
831
- var FILTER_COMPOSE = "var(--kb-blur,) var(--kb-brightness,) var(--kb-contrast,) var(--kb-grayscale,) var(--kb-hue-rotate,) var(--kb-invert,) var(--kb-saturate,) var(--kb-sepia,) var(--kb-drop-shadow,)";
832
- var BACKDROP_FILTER_COMPOSE = "var(--kb-backdrop-blur,) var(--kb-backdrop-brightness,) var(--kb-backdrop-contrast,) var(--kb-backdrop-grayscale,) var(--kb-backdrop-hue-rotate,) var(--kb-backdrop-invert,) var(--kb-backdrop-opacity,) var(--kb-backdrop-saturate,) var(--kb-backdrop-sepia,)";
833
- var _standalone = null;
834
- function buildStandalone() {
835
- const web = getEffectiveIsWeb();
836
- return {
837
- // Display
838
- // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
839
- // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
840
- // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
841
- // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
842
- // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
843
- // time, but without it these utilities couldn't re-show a `hidden` element on
844
- // native either (null → no style applied → still display:'none').
845
- flex: { display: "flex" },
846
- block: web ? { display: "block" } : null,
847
- "inline-block": { display: web ? "inline-block" : "flex" },
848
- inline: { display: web ? "inline" : "flex" },
849
- grid: web ? { display: "grid" } : null,
850
- grd: web ? { display: "grid" } : null,
851
- "inline-flex": { display: web ? "inline-flex" : "flex" },
852
- "inline-grid": { display: web ? "inline-grid" : "flex" },
853
- hidden: { display: "none" },
854
- contents: web ? { display: "contents" } : null,
855
- "flow-root": web ? { display: "flow-root" } : null,
856
- // Flex direction (both web and native)
857
- "flex-row": { flexDirection: "row" },
858
- "flex-col": { flexDirection: "column" },
859
- "flex-row-reverse": { flexDirection: "row-reverse" },
860
- "flex-col-reverse": { flexDirection: "column-reverse" },
861
- // Flex wrap (both)
862
- "flex-wrap": { flexWrap: "wrap" },
863
- "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
864
- "flex-nowrap": { flexWrap: "nowrap" },
865
- // Flex grow / shrink (both)
866
- "flex-grow": { flexGrow: 1 },
867
- "flex-grow-0": { flexGrow: 0 },
868
- "flex-shrink": { flexShrink: 1 },
869
- "flex-shrink-0": { flexShrink: 0 },
870
- // Align items (both)
871
- "items-start": { alignItems: "flex-start" },
872
- "items-end": { alignItems: "flex-end" },
873
- "items-center": { alignItems: "center" },
874
- "items-baseline": { alignItems: "baseline" },
875
- "items-stretch": { alignItems: "stretch" },
876
- // Justify content (both)
877
- "justify-start": { justifyContent: "flex-start" },
878
- "justify-end": { justifyContent: "flex-end" },
879
- "justify-center": { justifyContent: "center" },
880
- "justify-between": { justifyContent: "space-between" },
881
- "justify-around": { justifyContent: "space-around" },
882
- "justify-evenly": { justifyContent: "space-evenly" },
883
- // Align content (both)
884
- "content-start": { alignContent: "flex-start" },
885
- "content-end": { alignContent: "flex-end" },
886
- "content-center": { alignContent: "center" },
887
- "content-between": { alignContent: "space-between" },
888
- "content-around": { alignContent: "space-around" },
889
- "content-evenly": { alignContent: "space-evenly" },
890
- "content-stretch": { alignContent: "stretch" },
891
- // Align self (both)
892
- "self-auto": { alignSelf: "auto" },
893
- "self-start": { alignSelf: "flex-start" },
894
- "self-end": { alignSelf: "flex-end" },
895
- "self-center": { alignSelf: "center" },
896
- "self-stretch": { alignSelf: "stretch" },
897
- "self-baseline": { alignSelf: "baseline" },
898
- // Text align (both)
899
- "text-left": { textAlign: "left" },
900
- "text-right": { textAlign: "right" },
901
- "text-center": { textAlign: "center" },
902
- "text-justify": { textAlign: "justify" },
903
- // Font weight shortcuts (both)
904
- "font-thin": { fontWeight: "100" },
905
- "font-extralight": { fontWeight: "200" },
906
- "font-light": { fontWeight: "300" },
907
- "font-normal": { fontWeight: "400" },
908
- "font-medium": { fontWeight: "500" },
909
- "font-semibold": { fontWeight: "600" },
910
- "font-bold": { fontWeight: "700" },
911
- "font-extrabold": { fontWeight: "800" },
912
- "font-black": { fontWeight: "900" },
913
- // Text decoration (overline is web-only)
914
- underline: { textDecorationLine: "underline" },
915
- overline: web ? { textDecorationLine: "overline" } : null,
916
- "line-through": { textDecorationLine: "line-through" },
917
- "no-underline": { textDecorationLine: "none" },
918
- // Text transform (both)
919
- uppercase: { textTransform: "uppercase" },
920
- lowercase: { textTransform: "lowercase" },
921
- capitalize: { textTransform: "capitalize" },
922
- "normal-case": { textTransform: "none" },
923
- // Font style (both)
924
- italic: { fontStyle: "italic" },
925
- "non-italic": { fontStyle: "normal" },
926
- // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
927
- relative: { position: "relative" },
928
- absolute: { position: "absolute" },
929
- fixed: web ? { position: "fixed" } : null,
930
- sticky: web ? { position: "sticky" } : null,
931
- static: web ? { position: "static" } : null,
932
- // Visibility (no `visibility` prop on native; use opacity instead)
933
- visible: web ? { visibility: "visible" } : null,
934
- invisible: web ? { visibility: "hidden" } : { opacity: 0 },
935
- // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
936
- "overflow-hidden": { overflow: "hidden" },
937
- "overflow-visible": { overflow: "visible" },
938
- "overflow-scroll": { overflow: "scroll" },
939
- "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
940
- // overflowX / overflowY are web-only
941
- "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
942
- "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
943
- "overflow-x-auto": web ? { overflowX: "auto" } : null,
944
- "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
945
- "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
946
- "overflow-y-auto": web ? { overflowY: "auto" } : null,
947
- // Object fit (web-only; use resizeMode prop on RN Image)
948
- "object-contain": web ? { objectFit: "contain" } : null,
949
- "object-cover": web ? { objectFit: "cover" } : null,
950
- "object-fill": web ? { objectFit: "fill" } : null,
951
- "object-none": web ? { objectFit: "none" } : null,
952
- "object-scale-down": web ? { objectFit: "scale-down" } : null,
953
- // Border style (both)
954
- "border-solid": { borderStyle: "solid" },
955
- "border-dashed": { borderStyle: "dashed" },
956
- "border-dotted": { borderStyle: "dotted" },
957
- "border-none": { borderWidth: 0 },
958
- // Border sides default width (both)
959
- "border-t": { borderTopWidth: 1 },
960
- "border-r": { borderRightWidth: 1 },
961
- "border-b": { borderBottomWidth: 1 },
962
- "border-l": { borderLeftWidth: 1 },
963
- // Cursor (web-only)
964
- "cursor-auto": web ? { cursor: "auto" } : null,
965
- "cursor-default": web ? { cursor: "default" } : null,
966
- "cursor-pointer": web ? { cursor: "pointer" } : null,
967
- "cursor-wait": web ? { cursor: "wait" } : null,
968
- "cursor-text": web ? { cursor: "text" } : null,
969
- "cursor-move": web ? { cursor: "move" } : null,
970
- "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
971
- // User select (web-only)
972
- "select-none": web ? { userSelect: "none" } : null,
973
- "select-text": web ? { userSelect: "text" } : null,
974
- "select-all": web ? { userSelect: "all" } : null,
975
- "select-auto": web ? { userSelect: "auto" } : null,
976
- // Pointer events (both)
977
- "pointer-events-none": { pointerEvents: "none" },
978
- "pointer-events-auto": { pointerEvents: "auto" },
979
- // Whitespace (web-only; RN Text uses numberOfLines prop instead)
980
- "whitespace-normal": web ? { whiteSpace: "normal" } : null,
981
- "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
982
- "whitespace-pre": web ? { whiteSpace: "pre" } : null,
983
- "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
984
- "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
985
- // Word break (web-only)
986
- "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
987
- "break-words": web ? { overflowWrap: "break-word" } : null,
988
- "break-all": web ? { wordBreak: "break-all" } : null,
989
- // Misc web-only
990
- truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
991
- "box-border": web ? { boxSizing: "border-box" } : null,
992
- "box-content": web ? { boxSizing: "content-box" } : null,
993
- "appearance-none": web ? { appearance: "none" } : null,
994
- "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
995
- outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
996
- resize: web ? { resize: "both" } : null,
997
- "resize-none": web ? { resize: "none" } : null,
998
- "resize-y": web ? { resize: "vertical" } : null,
999
- "resize-x": web ? { resize: "horizontal" } : null,
1000
- antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
1001
- "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
1002
- "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
1003
- "sr-only": web ? {
1004
- position: "absolute",
1005
- width: 1,
1006
- height: 1,
1007
- padding: 0,
1008
- margin: -1,
1009
- overflow: "hidden",
1010
- clip: "rect(0,0,0,0)",
1011
- whiteSpace: "nowrap",
1012
- border: 0
1013
- } : null,
1014
- "not-sr-only": web ? {
1015
- position: "static",
1016
- width: "auto",
1017
- height: "auto",
1018
- padding: 0,
1019
- margin: 0,
1020
- overflow: "visible",
1021
- clip: "auto",
1022
- whiteSpace: "normal"
1023
- } : null,
1024
- // Lists (web-only)
1025
- "list-none": web ? { listStyleType: "none" } : null,
1026
- "list-disc": web ? { listStyleType: "disc" } : null,
1027
- "list-decimal": web ? { listStyleType: "decimal" } : null,
1028
- // Background clip (web-only)
1029
- "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
1030
- "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
1031
- "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
1032
- "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
1033
- // Background image
1034
- "bg-none": web ? { backgroundImage: "none" } : null,
1035
- // Background size (web-only)
1036
- "bg-auto": web ? { backgroundSize: "auto" } : null,
1037
- "bg-cover": web ? { backgroundSize: "cover" } : null,
1038
- "bg-contain": web ? { backgroundSize: "contain" } : null,
1039
- // Background position (web-only)
1040
- "bg-center": web ? { backgroundPosition: "center" } : null,
1041
- "bg-top": web ? { backgroundPosition: "top" } : null,
1042
- "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
1043
- "bg-left": web ? { backgroundPosition: "left" } : null,
1044
- "bg-right": web ? { backgroundPosition: "right" } : null,
1045
- "bg-left-top": web ? { backgroundPosition: "left top" } : null,
1046
- "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
1047
- "bg-right-top": web ? { backgroundPosition: "right top" } : null,
1048
- "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
1049
- // Background repeat (web-only)
1050
- "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
1051
- "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
1052
- "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
1053
- "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
1054
- "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
1055
- "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
1056
- // Background attachment (web-only)
1057
- "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
1058
- "bg-local": web ? { backgroundAttachment: "local" } : null,
1059
- "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
1060
- // Object position (web-only)
1061
- "object-center": web ? { objectPosition: "center" } : null,
1062
- "object-top": web ? { objectPosition: "top" } : null,
1063
- "object-bottom": web ? { objectPosition: "bottom" } : null,
1064
- "object-left": web ? { objectPosition: "left" } : null,
1065
- "object-right": web ? { objectPosition: "right" } : null,
1066
- "object-left-top": web ? { objectPosition: "left top" } : null,
1067
- "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1068
- "object-right-top": web ? { objectPosition: "right top" } : null,
1069
- "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1070
- // Table (web-only)
1071
- table: web ? { display: "table" } : null,
1072
- "table-auto": web ? { tableLayout: "auto" } : null,
1073
- "table-fixed": web ? { tableLayout: "fixed" } : null,
1074
- "caption-top": web ? { captionSide: "top" } : null,
1075
- "caption-bottom": web ? { captionSide: "bottom" } : null,
1076
- "border-collapse": web ? { borderCollapse: "collapse" } : null,
1077
- "border-separate": web ? { borderCollapse: "separate" } : null,
1078
- // List style position (web-only)
1079
- "list-inside": web ? { listStylePosition: "inside" } : null,
1080
- "list-outside": web ? { listStylePosition: "outside" } : null,
1081
- // Font variant numeric (web-only)
1082
- "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1083
- ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1084
- "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1085
- "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1086
- "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1087
- "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1088
- "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1089
- "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1090
- "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1091
- // Isolation (web-only)
1092
- isolate: web ? { isolation: "isolate" } : null,
1093
- "isolation-auto": web ? { isolation: "auto" } : null,
1094
- // Backface visibility (both iOS and Android)
1095
- "backface-visible": { backfaceVisibility: "visible" },
1096
- "backface-hidden": { backfaceVisibility: "hidden" },
1097
- // CSS filters — standalone = default/full effect (web-only)
1098
- grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1099
- invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1100
- sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1101
- // Divide none
1102
- "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1103
- // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1104
- group: web ? {} : null,
1105
- peer: web ? {} : null,
1106
- // Text-wrap (web-only)
1107
- "text-wrap": web ? { textWrap: "wrap" } : null,
1108
- "text-nowrap": web ? { textWrap: "nowrap" } : null,
1109
- "text-balance": web ? { textWrap: "balance" } : null,
1110
- "text-pretty": web ? { textWrap: "pretty" } : null,
1111
- // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1112
- // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1113
- // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1114
- // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1115
- // is unchanged since there's no dynamic chrome to account for.
1116
- // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1117
- "w-screen": web ? { width: "100dvw" } : null,
1118
- "min-h-screen": { minHeight: "100dvh" },
1119
- "max-h-screen": { maxHeight: "100dvh" },
1120
- "min-w-screen": web ? { minWidth: "100dvw" } : null,
1121
- "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1122
- // max-w named container sizes (mirrors Tailwind's container scale)
1123
- "max-w-none": { maxWidth: "none" },
1124
- "max-w-xs": { maxWidth: 320 },
1125
- "max-w-sm": { maxWidth: 384 },
1126
- "max-w-md": { maxWidth: 448 },
1127
- "max-w-lg": { maxWidth: 512 },
1128
- "max-w-xl": { maxWidth: 576 },
1129
- "max-w-2xl": { maxWidth: 672 },
1130
- "max-w-3xl": { maxWidth: 768 },
1131
- "max-w-4xl": { maxWidth: 896 },
1132
- "max-w-5xl": { maxWidth: 1024 },
1133
- "max-w-6xl": { maxWidth: 1152 },
1134
- "max-w-7xl": { maxWidth: 1280 },
1135
- "max-w-prose": web ? { maxWidth: "65ch" } : null,
1136
- // Extended cursors (web-only)
1137
- "cursor-grab": web ? { cursor: "grab" } : null,
1138
- "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1139
- "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1140
- "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1141
- "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1142
- "cursor-help": web ? { cursor: "help" } : null,
1143
- "cursor-none": web ? { cursor: "none" } : null,
1144
- // Overflow clip (web-only)
1145
- "overflow-clip": web ? { overflow: "clip" } : null,
1146
- "overflow-x-clip": web ? { overflowX: "clip" } : null,
1147
- "overflow-y-clip": web ? { overflowY: "clip" } : null,
1148
- // Scroll behavior (web-only)
1149
- "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1150
- "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1151
- // Float (web-only)
1152
- "float-left": web ? { float: "left" } : null,
1153
- "float-right": web ? { float: "right" } : null,
1154
- "float-start": web ? { float: "inline-start" } : null,
1155
- "float-end": web ? { float: "inline-end" } : null,
1156
- "float-none": web ? { float: "none" } : null,
1157
- // Clear (web-only)
1158
- "clear-left": web ? { clear: "left" } : null,
1159
- "clear-right": web ? { clear: "right" } : null,
1160
- "clear-both": web ? { clear: "both" } : null,
1161
- "clear-start": web ? { clear: "inline-start" } : null,
1162
- "clear-end": web ? { clear: "inline-end" } : null,
1163
- "clear-none": web ? { clear: "none" } : null,
1164
- // Vertical align (web-only)
1165
- "align-baseline": web ? { verticalAlign: "baseline" } : null,
1166
- "align-top": web ? { verticalAlign: "top" } : null,
1167
- "align-middle": web ? { verticalAlign: "middle" } : null,
1168
- "align-bottom": web ? { verticalAlign: "bottom" } : null,
1169
- "align-text-top": web ? { verticalAlign: "text-top" } : null,
1170
- "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1171
- "align-sub": web ? { verticalAlign: "sub" } : null,
1172
- "align-super": web ? { verticalAlign: "super" } : null,
1173
- // Touch action (web-only)
1174
- "touch-auto": web ? { touchAction: "auto" } : null,
1175
- "touch-none": web ? { touchAction: "none" } : null,
1176
- "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1177
- "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1178
- "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1179
- "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1180
- "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1181
- "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1182
- "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1183
- "touch-manipulation": web ? { touchAction: "manipulation" } : null
1184
- };
1185
- }
1186
- function getStandalone() {
1187
- if (!_standalone) _standalone = buildStandalone();
1188
- return _standalone;
1189
- }
1190
- function makeSpacingResolver(prop) {
1191
- return ({ value, negative, isArbitrary }, { spacing }) => {
1192
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
1193
- return v !== null ? { [prop]: v } : null;
1194
- };
945
+ function makeSpacingResolver(prop) {
946
+ return ({ value, negative, isArbitrary }, { spacing }) => {
947
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
948
+ return v !== null ? { [prop]: v } : null;
949
+ };
1195
950
  }
1196
951
  var PADDING_MARGIN_PROPS = {
1197
952
  p: "padding",
@@ -1209,101 +964,7 @@ var PADDING_MARGIN_PROPS = {
1209
964
  mb: "marginBottom",
1210
965
  ml: "marginLeft"
1211
966
  };
1212
- function makeBorderSideResolver(widthProp, colorProp) {
1213
- return ({ value, isArbitrary }, { colors, borderWidth }) => {
1214
- if (!value) return { [widthProp]: 1 };
1215
- if (isArbitrary) {
1216
- const w2 = toNativeValue(value);
1217
- if (typeof w2 === "number") return { [widthProp]: w2 };
1218
- return { [colorProp]: value };
1219
- }
1220
- const color = resolveColor(value, colors, false);
1221
- if (color) return { [colorProp]: color };
1222
- const w = borderWidth[value];
1223
- return w !== void 0 ? { [widthProp]: w } : null;
1224
- };
1225
- }
1226
- var BORDER_SIDE_PROPS = {
1227
- "border-t": ["borderTopWidth", "borderTopColor"],
1228
- "border-r": ["borderRightWidth", "borderRightColor"],
1229
- "border-b": ["borderBottomWidth", "borderBottomColor"],
1230
- "border-l": ["borderLeftWidth", "borderLeftColor"]
1231
- };
1232
- function keyframeDeclToCSS(decl) {
1233
- return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
1234
- }
1235
- function buildKeyframeCSS(name, steps) {
1236
- const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
1237
- return `@keyframes ${name} { ${body} }`;
1238
- }
1239
- var BUILTIN_ANIMATIONS = {
1240
- none: { animation: "none" },
1241
- spin: {
1242
- animation: "kb-spin 1s linear infinite",
1243
- __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
1244
- },
1245
- ping: {
1246
- animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
1247
- __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
1248
- },
1249
- pulse: {
1250
- animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
1251
- __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
1252
- },
1253
- bounce: {
1254
- animation: "kb-bounce 1s infinite",
1255
- __keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
1256
- }
1257
- };
1258
- function buildAnimationValue(animation, keyframes) {
1259
- const words = animation.trim().split(/\s+/);
1260
- const name = words[0];
1261
- if (!name) return { animation };
1262
- const builtin = BUILTIN_ANIMATIONS[name];
1263
- if (builtin?.__keyframe) {
1264
- const kbName = builtin.animation.split(/\s+/, 1)[0];
1265
- return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
1266
- }
1267
- const steps = keyframes?.[name];
1268
- return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
1269
- }
1270
- var RESOLVERS = {
1271
- // ── Background ─────────────────────────────────────────────────────────────
1272
- bg: ({ value, isArbitrary }, { colors }) => {
1273
- const color = resolveColor(value, colors, isArbitrary);
1274
- if (!color) return null;
1275
- return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1276
- },
1277
- "bg-opacity": ({ value, isArbitrary }, _) => {
1278
- if (!getEffectiveIsWeb()) return null;
1279
- const n = parseFloat(value);
1280
- if (isNaN(n)) return null;
1281
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1282
- return { "--bg-opacity": v };
1283
- },
1284
- // ── Text ───────────────────────────────────────────────────────────────────
1285
- text: ({ value, isArbitrary }, { colors, fontSize }) => {
1286
- const size = resolveFontSize(value, fontSize, false);
1287
- if (!isArbitrary && size !== null) return { fontSize: size };
1288
- if (isArbitrary) {
1289
- if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1290
- return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1291
- }
1292
- return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1293
- }
1294
- const color = resolveColor(value, colors, false);
1295
- if (!color) return null;
1296
- return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1297
- },
1298
- "text-opacity": ({ value, isArbitrary }, _) => {
1299
- if (!getEffectiveIsWeb()) return null;
1300
- const n = parseFloat(value);
1301
- if (isNaN(n)) return null;
1302
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1303
- return { "--text-opacity": v };
1304
- },
1305
- // ── Padding & margin ───────────────────────────────────────────────────────
1306
- // Generated below via makeSpacingResolver() — see PADDING_MARGIN_PROPS.
967
+ var spacingResolvers = {
1307
968
  // ── Sizing ─────────────────────────────────────────────────────────────────
1308
969
  w: ({ value, isArbitrary }, { spacing }) => {
1309
970
  const v = resolveSizing(value, spacing, isArbitrary);
@@ -1329,6 +990,126 @@ var RESOLVERS = {
1329
990
  const v = resolveSizing(value, spacing, isArbitrary);
1330
991
  return v !== null ? { maxHeight: v } : null;
1331
992
  },
993
+ // ── Size shorthand (sets width AND height in one utility) ────────────────
994
+ size: ({ value, isArbitrary }, { spacing }) => {
995
+ const v = resolveSizing(value, spacing, isArbitrary);
996
+ return v !== null ? { width: v, height: v } : null;
997
+ },
998
+ // ── Flex basis ─────────────────────────────────────────────────────────────
999
+ basis: ({ value, isArbitrary }, { spacing }) => {
1000
+ const v = resolveSizing(value, spacing, isArbitrary);
1001
+ return v !== null ? { flexBasis: v } : null;
1002
+ },
1003
+ // ── Gap ────────────────────────────────────────────────────────────────────
1004
+ gap: ({ value, negative, isArbitrary }, { spacing }) => {
1005
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1006
+ return v !== null ? { gap: v } : null;
1007
+ },
1008
+ "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
1009
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1010
+ return v !== null ? { columnGap: v } : null;
1011
+ },
1012
+ "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
1013
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1014
+ return v !== null ? { rowGap: v } : null;
1015
+ },
1016
+ // ── Position ───────────────────────────────────────────────────────────────
1017
+ top: ({ value, negative, isArbitrary }, { spacing }) => {
1018
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1019
+ return v !== null ? { top: v } : null;
1020
+ },
1021
+ right: ({ value, negative, isArbitrary }, { spacing }) => {
1022
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1023
+ return v !== null ? { right: v } : null;
1024
+ },
1025
+ bottom: ({ value, negative, isArbitrary }, { spacing }) => {
1026
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1027
+ return v !== null ? { bottom: v } : null;
1028
+ },
1029
+ left: ({ value, negative, isArbitrary }, { spacing }) => {
1030
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1031
+ return v !== null ? { left: v } : null;
1032
+ },
1033
+ inset: ({ value, negative, isArbitrary }, { spacing }) => {
1034
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1035
+ return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
1036
+ },
1037
+ "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
1038
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1039
+ return v !== null ? { left: v, right: v } : null;
1040
+ },
1041
+ "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
1042
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1043
+ return v !== null ? { top: v, bottom: v } : null;
1044
+ },
1045
+ // ── Translate ──────────────────────────────────────────────────────────────
1046
+ "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
1047
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1048
+ if (v === null) return null;
1049
+ if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
1050
+ if (typeof v === "string") {
1051
+ const n = parseFloat(v);
1052
+ return isNaN(n) ? null : { transform: [{ translateX: n }] };
1053
+ }
1054
+ return { transform: [{ translateX: v }] };
1055
+ },
1056
+ "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
1057
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1058
+ if (v === null) return null;
1059
+ if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
1060
+ if (typeof v === "string") {
1061
+ const n = parseFloat(v);
1062
+ return isNaN(n) ? null : { transform: [{ translateY: n }] };
1063
+ }
1064
+ return { transform: [{ translateY: v }] };
1065
+ },
1066
+ // ── Space between ─────────────────────────────────────────────────────────
1067
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
1068
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
1069
+ "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
1070
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1071
+ if (v === null) return null;
1072
+ if (getEffectiveIsWeb()) return { __spaceX: v };
1073
+ return { columnGap: v };
1074
+ },
1075
+ "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
1076
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
1077
+ if (v === null) return null;
1078
+ if (getEffectiveIsWeb()) return { __spaceY: v };
1079
+ return { rowGap: v };
1080
+ }
1081
+ };
1082
+ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
1083
+ spacingResolvers[utility] = makeSpacingResolver(prop);
1084
+ }
1085
+
1086
+ // src/core/resolvers/border.ts
1087
+ function resolveRadius(value, radii, isArbitrary) {
1088
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
1089
+ const key = value === "" ? "DEFAULT" : value;
1090
+ return radii[key] ?? null;
1091
+ }
1092
+ function makeBorderSideResolver(widthProp, colorProp) {
1093
+ return ({ value, isArbitrary }, { colors, borderWidth }) => {
1094
+ if (!value) return { [widthProp]: 1 };
1095
+ if (isArbitrary) {
1096
+ const w2 = toNativeValue(value);
1097
+ if (typeof w2 === "number") return { [widthProp]: w2 };
1098
+ return { [colorProp]: value };
1099
+ }
1100
+ const color = resolveColor(value, colors, false);
1101
+ if (color) return { [colorProp]: color };
1102
+ const w = borderWidth[value];
1103
+ return w !== void 0 ? { [widthProp]: w } : null;
1104
+ };
1105
+ }
1106
+ var BORDER_SIDE_PROPS = {
1107
+ "border-t": ["borderTopWidth", "borderTopColor"],
1108
+ "border-r": ["borderRightWidth", "borderRightColor"],
1109
+ "border-b": ["borderBottomWidth", "borderBottomColor"],
1110
+ "border-l": ["borderLeftWidth", "borderLeftColor"]
1111
+ };
1112
+ var borderResolvers = {
1332
1113
  // ── Border width ───────────────────────────────────────────────────────────
1333
1114
  border: ({ value, isArbitrary }, { colors, borderWidth, spacing }) => {
1334
1115
  if (!value) return { borderWidth: borderWidth["DEFAULT"] ?? 1 };
@@ -1385,188 +1166,128 @@ var RESOLVERS = {
1385
1166
  const r = resolveRadius(value, borderRadius, isArbitrary);
1386
1167
  return r !== null ? { borderBottomRightRadius: r } : null;
1387
1168
  },
1388
- // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1389
- grid: ({ value, isArbitrary }) => {
1390
- if (!value) return null;
1391
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1392
- const n = parseInt(value, 10);
1393
- if (isNaN(n) || n < 1 || n > 12) return null;
1394
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1395
- },
1396
- // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1397
- "grid-cols": ({ value, isArbitrary }) => {
1169
+ // ── Outline extended (web-only) ───────────────────────────────────────────
1170
+ outline: ({ value, isArbitrary }, { colors }) => {
1398
1171
  if (!getEffectiveIsWeb()) return null;
1399
- if (!value) return null;
1400
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1401
- if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1402
- const n = parseInt(value, 10);
1403
- if (isNaN(n) || n < 1 || n > 12) return null;
1404
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1172
+ if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1173
+ if (value === "none") return { outline: "none", outlineOffset: "0" };
1174
+ if (isArbitrary) {
1175
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1176
+ return { outlineColor: value };
1177
+ }
1178
+ const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1179
+ if (value in widths) return { outlineWidth: widths[value] };
1180
+ const color = resolveColor(value, colors, false);
1181
+ return color ? { outlineColor: color } : null;
1405
1182
  },
1406
- "grid-rows": ({ value, isArbitrary }) => {
1183
+ "outline-offset": ({ value, isArbitrary }) => {
1407
1184
  if (!getEffectiveIsWeb()) return null;
1408
- if (!value) return null;
1409
- if (isArbitrary) return { gridTemplateRows: value };
1410
- if (value === "none") return { gridTemplateRows: "none" };
1411
- const n = parseInt(value, 10);
1412
- if (isNaN(n) || n < 1) return null;
1413
- return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1185
+ if (isArbitrary) return { outlineOffset: value };
1186
+ const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1187
+ return offsets[value] ? { outlineOffset: offsets[value] } : null;
1414
1188
  },
1415
- // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1416
- "grid-flow": ({ value }) => {
1189
+ // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1190
+ // These return special __divide* markers that resolver.ts converts to
1191
+ // .cls > * + * { border-... } CSS rules. No inline style is applied.
1192
+ "divide-x": ({ value, isArbitrary }) => {
1417
1193
  if (!getEffectiveIsWeb()) return null;
1418
- const flows = {
1419
- row: "row",
1420
- col: "column",
1421
- dense: "dense",
1422
- "row-dense": "row dense",
1423
- "col-dense": "column dense"
1424
- };
1425
- const flow = flows[value];
1426
- return flow ? { gridAutoFlow: flow } : null;
1194
+ if (value === "reverse") return null;
1195
+ if (!value) return { __divideX: 1 };
1196
+ const n = parseFloat(value);
1197
+ return isNaN(n) ? null : { __divideX: n };
1427
1198
  },
1428
- // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1429
- "auto-cols": ({ value, isArbitrary }) => {
1199
+ "divide-y": ({ value, isArbitrary }) => {
1430
1200
  if (!getEffectiveIsWeb()) return null;
1431
- if (isArbitrary) return { gridAutoColumns: value };
1432
- const presets = {
1433
- auto: "auto",
1434
- min: "min-content",
1435
- max: "max-content",
1436
- fr: "minmax(0, 1fr)"
1437
- };
1438
- const v = presets[value];
1439
- return v ? { gridAutoColumns: v } : null;
1201
+ if (value === "reverse") return null;
1202
+ if (!value) return { __divideY: 1 };
1203
+ const n = parseFloat(value);
1204
+ return isNaN(n) ? null : { __divideY: n };
1440
1205
  },
1441
- "auto-rows": ({ value, isArbitrary }) => {
1206
+ divide: ({ value, isArbitrary }, { colors }) => {
1442
1207
  if (!getEffectiveIsWeb()) return null;
1443
- if (isArbitrary) return { gridAutoRows: value };
1444
- const presets = {
1445
- auto: "auto",
1446
- min: "min-content",
1447
- max: "max-content",
1448
- fr: "minmax(0, 1fr)"
1208
+ if (isArbitrary) return { __divideColor: value };
1209
+ const styleTokens = {
1210
+ solid: "solid",
1211
+ dashed: "dashed",
1212
+ dotted: "dotted",
1213
+ double: "double",
1214
+ none: "none"
1449
1215
  };
1450
- const v = presets[value];
1451
- return v ? { gridAutoRows: v } : null;
1452
- },
1453
- // ── Grid column placement (web-only) ──────────────────────────────────────
1454
- "col-span": ({ value }) => {
1455
- if (!getEffectiveIsWeb()) return null;
1456
- if (value === "full") return { gridColumn: "1 / -1" };
1457
- const n = parseInt(value, 10);
1458
- return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1459
- },
1460
- "col-start": ({ value, isArbitrary }) => {
1461
- if (!getEffectiveIsWeb()) return null;
1462
- if (value === "auto") return { gridColumnStart: "auto" };
1463
- if (isArbitrary) return { gridColumnStart: value };
1464
- const n = parseInt(value, 10);
1465
- return isNaN(n) ? null : { gridColumnStart: n };
1466
- },
1467
- "col-end": ({ value, isArbitrary }) => {
1468
- if (!getEffectiveIsWeb()) return null;
1469
- if (value === "auto") return { gridColumnEnd: "auto" };
1470
- if (isArbitrary) return { gridColumnEnd: value };
1471
- const n = parseInt(value, 10);
1472
- return isNaN(n) ? null : { gridColumnEnd: n };
1473
- },
1474
- col: ({ value, isArbitrary }) => {
1475
- if (!getEffectiveIsWeb()) return null;
1476
- if (!value || value === "auto") return { gridColumn: "auto" };
1477
- if (isArbitrary) return { gridColumn: value };
1478
- return null;
1479
- },
1480
- // ── Grid row placement (web-only) ─────────────────────────────────────────
1481
- "row-span": ({ value }) => {
1482
- if (!getEffectiveIsWeb()) return null;
1483
- if (value === "full") return { gridRow: "1 / -1" };
1484
- const n = parseInt(value, 10);
1485
- return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1486
- },
1487
- "row-start": ({ value, isArbitrary }) => {
1488
- if (!getEffectiveIsWeb()) return null;
1489
- if (value === "auto") return { gridRowStart: "auto" };
1490
- if (isArbitrary) return { gridRowStart: value };
1491
- const n = parseInt(value, 10);
1492
- return isNaN(n) ? null : { gridRowStart: n };
1493
- },
1494
- "row-end": ({ value, isArbitrary }) => {
1495
- if (!getEffectiveIsWeb()) return null;
1496
- if (value === "auto") return { gridRowEnd: "auto" };
1497
- if (isArbitrary) return { gridRowEnd: value };
1498
- const n = parseInt(value, 10);
1499
- return isNaN(n) ? null : { gridRowEnd: n };
1216
+ if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1217
+ const color = resolveColor(value, colors, false);
1218
+ return color ? { __divideColor: color } : null;
1500
1219
  },
1501
- row: ({ value, isArbitrary }) => {
1502
- if (!getEffectiveIsWeb()) return null;
1503
- if (!value || value === "auto") return { gridRow: "auto" };
1504
- if (isArbitrary) return { gridRow: value };
1220
+ // ── Ring ───────────────────────────────────────────────────────────────────
1221
+ // Web: box-shadow outline ring (doesn't affect layout).
1222
+ // Native: React Native has no box-shadow, so this falls back to borderWidth/
1223
+ // borderColor — the closest visual approximation (used by other RN Tailwind-
1224
+ // likes for the same reason). Unlike the web ring, this DOES affect layout,
1225
+ // and it shares its properties with the `border` utility — combining
1226
+ // `border-*` and `ring-*` on the same native element means whichever class
1227
+ // comes later wins, since both ultimately set borderWidth/borderColor.
1228
+ ring: ({ value, isArbitrary }, { colors }) => {
1229
+ const onWeb = getEffectiveIsWeb();
1230
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1231
+ const DEFAULT_WIDTH = 3;
1232
+ const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
1233
+ if (!value) {
1234
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1235
+ }
1236
+ if (value === "inset") {
1237
+ return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1238
+ }
1239
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1240
+ if (!isArbitrary && value in widthTokens) {
1241
+ const w = widthTokens[value];
1242
+ return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
1243
+ }
1244
+ if (isArbitrary) {
1245
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1246
+ if (numMatch) {
1247
+ const unit = numMatch[2] ?? "px";
1248
+ if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1249
+ return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
1250
+ }
1251
+ return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
1252
+ }
1253
+ const color = resolveColor(value, colors, false);
1254
+ if (color) {
1255
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
1256
+ }
1505
1257
  return null;
1506
1258
  },
1507
- // ── Grid alignment (web-only) ──────────────────────────────────────────────
1508
- // place-items is shorthand for align-items + justify-items on a grid container
1509
- "place-items": ({ value }) => {
1510
- if (!getEffectiveIsWeb()) return null;
1511
- const map = {
1512
- start: "start",
1513
- end: "end",
1514
- center: "center",
1515
- stretch: "stretch",
1516
- baseline: "baseline"
1517
- };
1518
- return map[value] ? { placeItems: map[value] } : null;
1519
- },
1520
- // place-content is shorthand for align-content + justify-content on a grid container
1521
- "place-content": ({ value }) => {
1522
- if (!getEffectiveIsWeb()) return null;
1523
- const map = {
1524
- start: "start",
1525
- end: "end",
1526
- center: "center",
1527
- stretch: "stretch",
1528
- between: "space-between",
1529
- around: "space-around",
1530
- evenly: "space-evenly",
1531
- baseline: "baseline"
1532
- };
1533
- return map[value] ? { placeContent: map[value] } : null;
1534
- },
1535
- // justify-items controls inline-axis alignment of grid items within their cells
1536
- "justify-items": ({ value }) => {
1537
- if (!getEffectiveIsWeb()) return null;
1538
- const map = {
1539
- start: "start",
1540
- end: "end",
1541
- center: "center",
1542
- stretch: "stretch"
1543
- };
1544
- return map[value] ? { justifyItems: map[value] } : null;
1545
- },
1546
- // place-self is shorthand for align-self + justify-self on a grid item
1547
- "place-self": ({ value }) => {
1548
- if (!getEffectiveIsWeb()) return null;
1549
- const map = {
1550
- auto: "auto",
1551
- start: "start",
1552
- end: "end",
1553
- center: "center",
1554
- stretch: "stretch"
1555
- };
1556
- return map[value] ? { placeSelf: map[value] } : null;
1557
- },
1558
- // justify-self controls inline-axis self-alignment of a grid item
1559
- "justify-self": ({ value }) => {
1259
+ // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
1260
+ // create a gap between the element and the ring. There's no native
1261
+ // equivalent to approximate that with (a border can't create a gap outside
1262
+ // its own element without an extra wrapper view), so this stays a no-op
1263
+ // on native rather than rendering a misleading half-translation.
1264
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1560
1265
  if (!getEffectiveIsWeb()) return null;
1561
- const map = {
1562
- auto: "auto",
1563
- start: "start",
1564
- end: "end",
1565
- center: "center",
1566
- stretch: "stretch"
1266
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1267
+ const DEFAULT_RING_WIDTH = 3;
1268
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1269
+ let offsetWidth;
1270
+ if (!isArbitrary) {
1271
+ offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
1272
+ } else {
1273
+ offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
1274
+ }
1275
+ if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
1276
+ const isZero = offsetWidth === 0 || offsetWidth === "0";
1277
+ const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
1278
+ return {
1279
+ boxShadow: isZero ? `0 0 0 ${DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}` : `0 0 0 ${offsetCss} #fff, 0 0 0 calc(${offsetCss} + ${DEFAULT_RING_WIDTH}px) ${DEFAULT_RING_COLOR}`
1567
1280
  };
1568
- return map[value] ? { justifySelf: map[value] } : null;
1569
- },
1281
+ }
1282
+ };
1283
+ for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
1284
+ borderResolvers[utility] = makeBorderSideResolver(widthProp, colorProp);
1285
+ }
1286
+
1287
+ // src/core/resolvers/filters.ts
1288
+ var FILTER_COMPOSE = "var(--kb-blur,) var(--kb-brightness,) var(--kb-contrast,) var(--kb-grayscale,) var(--kb-hue-rotate,) var(--kb-invert,) var(--kb-saturate,) var(--kb-sepia,) var(--kb-drop-shadow,)";
1289
+ var BACKDROP_FILTER_COMPOSE = "var(--kb-backdrop-blur,) var(--kb-backdrop-brightness,) var(--kb-backdrop-contrast,) var(--kb-backdrop-grayscale,) var(--kb-backdrop-hue-rotate,) var(--kb-backdrop-invert,) var(--kb-backdrop-opacity,) var(--kb-backdrop-saturate,) var(--kb-backdrop-sepia,)";
1290
+ var filterResolvers = {
1570
1291
  // ── CSS Filters (composable via CSS variables, web-only) ─────────────────
1571
1292
  blur: ({ value, isArbitrary }) => {
1572
1293
  if (!getEffectiveIsWeb()) return null;
@@ -1701,226 +1422,31 @@ var RESOLVERS = {
1701
1422
  if (value === "none") return { backdropFilter: "none" };
1702
1423
  return null;
1703
1424
  },
1704
- // ── Background gradient (CSS variable gradient stops, web-only) ──────────
1705
- "bg-gradient-to": ({ value }) => {
1425
+ // ── Mix / background blend mode (web-only) ───────────────────────────────
1426
+ "mix-blend": ({ value }) => {
1706
1427
  if (!getEffectiveIsWeb()) return null;
1707
- const directions = {
1708
- t: "to top",
1709
- tr: "to top right",
1710
- r: "to right",
1711
- br: "to bottom right",
1712
- b: "to bottom",
1713
- bl: "to bottom left",
1714
- l: "to left",
1715
- tl: "to top left"
1716
- };
1717
- const dir = directions[value];
1718
- if (!dir) return null;
1719
- return {
1720
- backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
1721
- };
1428
+ const modes = [
1429
+ "normal",
1430
+ "multiply",
1431
+ "screen",
1432
+ "overlay",
1433
+ "darken",
1434
+ "lighten",
1435
+ "color-dodge",
1436
+ "color-burn",
1437
+ "hard-light",
1438
+ "soft-light",
1439
+ "difference",
1440
+ "exclusion",
1441
+ "hue",
1442
+ "saturation",
1443
+ "color",
1444
+ "luminosity",
1445
+ "plus-lighter"
1446
+ ];
1447
+ return modes.includes(value) ? { mixBlendMode: value } : null;
1722
1448
  },
1723
- from: ({ value, isArbitrary }, { colors }) => {
1724
- if (!getEffectiveIsWeb()) return null;
1725
- const color = resolveColor(value, colors, isArbitrary);
1726
- if (!color) return null;
1727
- return {
1728
- "--kb-gradient-from": color,
1729
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
1730
- };
1731
- },
1732
- via: ({ value, isArbitrary }, { colors }) => {
1733
- if (!getEffectiveIsWeb()) return null;
1734
- const color = resolveColor(value, colors, isArbitrary);
1735
- if (!color) return null;
1736
- return {
1737
- "--kb-gradient-via": color,
1738
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
1739
- };
1740
- },
1741
- to: ({ value, isArbitrary }, { colors }) => {
1742
- if (!getEffectiveIsWeb()) return null;
1743
- const color = resolveColor(value, colors, isArbitrary);
1744
- if (!color) return null;
1745
- return { "--kb-gradient-to": color };
1746
- },
1747
- // ── Transform extras (web-only) ───────────────────────────────────────────
1748
- "skew-x": ({ value, negative, isArbitrary }) => {
1749
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1750
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1751
- return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
1752
- },
1753
- "skew-y": ({ value, negative, isArbitrary }) => {
1754
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1755
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1756
- return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
1757
- },
1758
- // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
1759
- transform: ({ value, isArbitrary }) => {
1760
- if (!getEffectiveIsWeb()) return null;
1761
- if (isArbitrary) return { transform: value.replace(/_/g, " ") };
1762
- if (value === "none") return { transform: "none" };
1763
- return null;
1764
- },
1765
- // Transform origin
1766
- origin: ({ value, isArbitrary }) => {
1767
- if (!getEffectiveIsWeb()) return null;
1768
- if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
1769
- const origins = {
1770
- center: "center",
1771
- top: "top",
1772
- "top-right": "top right",
1773
- right: "right",
1774
- "bottom-right": "bottom right",
1775
- bottom: "bottom",
1776
- "bottom-left": "bottom left",
1777
- left: "left",
1778
- "top-left": "top left"
1779
- };
1780
- return origins[value] ? { transformOrigin: origins[value] } : null;
1781
- },
1782
- // ── Text decoration advanced (web-only) ───────────────────────────────────
1783
- decoration: ({ value, isArbitrary }, { colors }) => {
1784
- if (!getEffectiveIsWeb()) return null;
1785
- if (isArbitrary) {
1786
- if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
1787
- return { textDecorationColor: value };
1788
- }
1789
- const thickTokens = {
1790
- auto: "auto",
1791
- "from-font": "from-font",
1792
- "0": "0px",
1793
- "1": "1px",
1794
- "2": "2px",
1795
- "4": "4px",
1796
- "8": "8px"
1797
- };
1798
- if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
1799
- const styleTokens = {
1800
- solid: "solid",
1801
- dashed: "dashed",
1802
- dotted: "dotted",
1803
- double: "double",
1804
- wavy: "wavy"
1805
- };
1806
- if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
1807
- const color = resolveColor(value, colors, false);
1808
- return color ? { textDecorationColor: color } : null;
1809
- },
1810
- "underline-offset": ({ value, isArbitrary }) => {
1811
- if (!getEffectiveIsWeb()) return null;
1812
- if (isArbitrary) return { textUnderlineOffset: value };
1813
- const offsets = {
1814
- auto: "auto",
1815
- "0": "0px",
1816
- "1": "1px",
1817
- "2": "2px",
1818
- "4": "4px",
1819
- "8": "8px"
1820
- };
1821
- return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
1822
- },
1823
- // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
1824
- // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
1825
- // the literal string 'you can see it' quotes included), so it passes straight through as a
1826
- // valid `content: '*'` CSS value with no extra wrapping needed.
1827
- content: ({ value, isArbitrary }) => {
1828
- if (!getEffectiveIsWeb()) return null;
1829
- if (isArbitrary) return { content: value };
1830
- const presets = { none: "none" };
1831
- return value in presets ? { content: presets[value] } : null;
1832
- },
1833
- // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
1834
- // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
1835
- // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
1836
- // presumably a typo) parses, which is why this only fires when the arbitrary value
1837
- // itself contains a "property:value" pair; anything else (no colon) still resolves
1838
- // to null exactly as before this resolver existed, rather than guessing a property.
1839
- //
1840
- // Only the FIRST colon splits property from value — the value itself may contain
1841
- // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
1842
- // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
1843
- // custom property) key, so it can be used as the style object key as-written with no
1844
- // extra case conversion — it only needs to look like a plausible CSS property name.
1845
- "": ({ value, isArbitrary, negative }) => {
1846
- if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
1847
- if (negative) return null;
1848
- const colonIdx = value.indexOf(":");
1849
- if (colonIdx <= 0) return null;
1850
- const prop = value.slice(0, colonIdx).trim();
1851
- const cssValue = value.slice(colonIdx + 1).trim();
1852
- if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
1853
- return { [prop]: cssValue };
1854
- },
1855
- // ── Outline extended (web-only) ───────────────────────────────────────────
1856
- outline: ({ value, isArbitrary }, { colors }) => {
1857
- if (!getEffectiveIsWeb()) return null;
1858
- if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1859
- if (value === "none") return { outline: "none", outlineOffset: "0" };
1860
- if (isArbitrary) {
1861
- if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1862
- return { outlineColor: value };
1863
- }
1864
- const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1865
- if (value in widths) return { outlineWidth: widths[value] };
1866
- const color = resolveColor(value, colors, false);
1867
- return color ? { outlineColor: color } : null;
1868
- },
1869
- "outline-offset": ({ value, isArbitrary }) => {
1870
- if (!getEffectiveIsWeb()) return null;
1871
- if (isArbitrary) return { outlineOffset: value };
1872
- const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1873
- return offsets[value] ? { outlineOffset: offsets[value] } : null;
1874
- },
1875
- // ── Columns (web-only) ─────────────────────────────────────────────────────
1876
- columns: ({ value, isArbitrary }) => {
1877
- if (!getEffectiveIsWeb()) return null;
1878
- if (isArbitrary) return { columnCount: value };
1879
- if (value === "auto") return { columnCount: "auto" };
1880
- const n = parseInt(value, 10);
1881
- if (!isNaN(n)) return { columnCount: n };
1882
- const widths = {
1883
- "3xs": "16rem",
1884
- "2xs": "18rem",
1885
- xs: "20rem",
1886
- sm: "24rem",
1887
- md: "28rem",
1888
- lg: "32rem",
1889
- xl: "36rem",
1890
- "2xl": "42rem",
1891
- "3xl": "48rem",
1892
- "4xl": "56rem",
1893
- "5xl": "64rem",
1894
- "6xl": "72rem",
1895
- "7xl": "80rem"
1896
- };
1897
- return widths[value] ? { columnWidth: widths[value] } : null;
1898
- },
1899
- // ── Mix / background blend mode (web-only) ───────────────────────────────
1900
- "mix-blend": ({ value }) => {
1901
- if (!getEffectiveIsWeb()) return null;
1902
- const modes = [
1903
- "normal",
1904
- "multiply",
1905
- "screen",
1906
- "overlay",
1907
- "darken",
1908
- "lighten",
1909
- "color-dodge",
1910
- "color-burn",
1911
- "hard-light",
1912
- "soft-light",
1913
- "difference",
1914
- "exclusion",
1915
- "hue",
1916
- "saturation",
1917
- "color",
1918
- "luminosity",
1919
- "plus-lighter"
1920
- ];
1921
- return modes.includes(value) ? { mixBlendMode: value } : null;
1922
- },
1923
- "bg-blend": ({ value }) => {
1449
+ "bg-blend": ({ value }) => {
1924
1450
  if (!getEffectiveIsWeb()) return null;
1925
1451
  const modes = [
1926
1452
  "normal",
@@ -1953,237 +1479,582 @@ var RESOLVERS = {
1953
1479
  transform: "transform"
1954
1480
  };
1955
1481
  return presets[value] ? { willChange: presets[value] } : null;
1482
+ }
1483
+ };
1484
+
1485
+ // src/core/resolvers/layout.ts
1486
+ var _standalone = null;
1487
+ function buildStandalone() {
1488
+ const web = getEffectiveIsWeb();
1489
+ return {
1490
+ // Display
1491
+ // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
1492
+ // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
1493
+ // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
1494
+ // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
1495
+ // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
1496
+ // time, but without it these utilities couldn't re-show a `hidden` element on
1497
+ // native either (null → no style applied → still display:'none').
1498
+ flex: { display: "flex" },
1499
+ block: web ? { display: "block" } : null,
1500
+ "inline-block": { display: web ? "inline-block" : "flex" },
1501
+ inline: { display: web ? "inline" : "flex" },
1502
+ grid: web ? { display: "grid" } : null,
1503
+ grd: web ? { display: "grid" } : null,
1504
+ "inline-flex": { display: web ? "inline-flex" : "flex" },
1505
+ "inline-grid": { display: web ? "inline-grid" : "flex" },
1506
+ hidden: { display: "none" },
1507
+ contents: web ? { display: "contents" } : null,
1508
+ "flow-root": web ? { display: "flow-root" } : null,
1509
+ // Flex direction (both web and native)
1510
+ "flex-row": { flexDirection: "row" },
1511
+ "flex-col": { flexDirection: "column" },
1512
+ "flex-row-reverse": { flexDirection: "row-reverse" },
1513
+ "flex-col-reverse": { flexDirection: "column-reverse" },
1514
+ // Flex wrap (both)
1515
+ "flex-wrap": { flexWrap: "wrap" },
1516
+ "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
1517
+ "flex-nowrap": { flexWrap: "nowrap" },
1518
+ // Flex grow / shrink (both)
1519
+ "flex-grow": { flexGrow: 1 },
1520
+ "flex-grow-0": { flexGrow: 0 },
1521
+ "flex-shrink": { flexShrink: 1 },
1522
+ "flex-shrink-0": { flexShrink: 0 },
1523
+ // Align items (both)
1524
+ "items-start": { alignItems: "flex-start" },
1525
+ "items-end": { alignItems: "flex-end" },
1526
+ "items-center": { alignItems: "center" },
1527
+ "items-baseline": { alignItems: "baseline" },
1528
+ "items-stretch": { alignItems: "stretch" },
1529
+ // Justify content (both)
1530
+ "justify-start": { justifyContent: "flex-start" },
1531
+ "justify-end": { justifyContent: "flex-end" },
1532
+ "justify-center": { justifyContent: "center" },
1533
+ "justify-between": { justifyContent: "space-between" },
1534
+ "justify-around": { justifyContent: "space-around" },
1535
+ "justify-evenly": { justifyContent: "space-evenly" },
1536
+ // Align content (both)
1537
+ "content-start": { alignContent: "flex-start" },
1538
+ "content-end": { alignContent: "flex-end" },
1539
+ "content-center": { alignContent: "center" },
1540
+ "content-between": { alignContent: "space-between" },
1541
+ "content-around": { alignContent: "space-around" },
1542
+ "content-evenly": { alignContent: "space-evenly" },
1543
+ "content-stretch": { alignContent: "stretch" },
1544
+ // Align self (both)
1545
+ "self-auto": { alignSelf: "auto" },
1546
+ "self-start": { alignSelf: "flex-start" },
1547
+ "self-end": { alignSelf: "flex-end" },
1548
+ "self-center": { alignSelf: "center" },
1549
+ "self-stretch": { alignSelf: "stretch" },
1550
+ "self-baseline": { alignSelf: "baseline" },
1551
+ // Text align (both)
1552
+ "text-left": { textAlign: "left" },
1553
+ "text-right": { textAlign: "right" },
1554
+ "text-center": { textAlign: "center" },
1555
+ "text-justify": { textAlign: "justify" },
1556
+ // Font weight shortcuts (both)
1557
+ "font-thin": { fontWeight: "100" },
1558
+ "font-extralight": { fontWeight: "200" },
1559
+ "font-light": { fontWeight: "300" },
1560
+ "font-normal": { fontWeight: "400" },
1561
+ "font-medium": { fontWeight: "500" },
1562
+ "font-semibold": { fontWeight: "600" },
1563
+ "font-bold": { fontWeight: "700" },
1564
+ "font-extrabold": { fontWeight: "800" },
1565
+ "font-black": { fontWeight: "900" },
1566
+ // Text decoration (overline is web-only)
1567
+ underline: { textDecorationLine: "underline" },
1568
+ overline: web ? { textDecorationLine: "overline" } : null,
1569
+ "line-through": { textDecorationLine: "line-through" },
1570
+ "no-underline": { textDecorationLine: "none" },
1571
+ // Text transform (both)
1572
+ uppercase: { textTransform: "uppercase" },
1573
+ lowercase: { textTransform: "lowercase" },
1574
+ capitalize: { textTransform: "capitalize" },
1575
+ "normal-case": { textTransform: "none" },
1576
+ // Font style (both)
1577
+ italic: { fontStyle: "italic" },
1578
+ "non-italic": { fontStyle: "normal" },
1579
+ // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
1580
+ relative: { position: "relative" },
1581
+ absolute: { position: "absolute" },
1582
+ fixed: web ? { position: "fixed" } : null,
1583
+ sticky: web ? { position: "sticky" } : null,
1584
+ static: web ? { position: "static" } : null,
1585
+ // Visibility (no `visibility` prop on native; use opacity instead)
1586
+ visible: web ? { visibility: "visible" } : null,
1587
+ invisible: web ? { visibility: "hidden" } : { opacity: 0 },
1588
+ // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
1589
+ "overflow-hidden": { overflow: "hidden" },
1590
+ "overflow-visible": { overflow: "visible" },
1591
+ "overflow-scroll": { overflow: "scroll" },
1592
+ "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
1593
+ // overflowX / overflowY are web-only
1594
+ "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
1595
+ "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
1596
+ "overflow-x-auto": web ? { overflowX: "auto" } : null,
1597
+ "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
1598
+ "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
1599
+ "overflow-y-auto": web ? { overflowY: "auto" } : null,
1600
+ // Object fit (web-only; use resizeMode prop on RN Image)
1601
+ "object-contain": web ? { objectFit: "contain" } : null,
1602
+ "object-cover": web ? { objectFit: "cover" } : null,
1603
+ "object-fill": web ? { objectFit: "fill" } : null,
1604
+ "object-none": web ? { objectFit: "none" } : null,
1605
+ "object-scale-down": web ? { objectFit: "scale-down" } : null,
1606
+ // Border style (both)
1607
+ "border-solid": { borderStyle: "solid" },
1608
+ "border-dashed": { borderStyle: "dashed" },
1609
+ "border-dotted": { borderStyle: "dotted" },
1610
+ "border-none": { borderWidth: 0 },
1611
+ // Border sides default width (both)
1612
+ "border-t": { borderTopWidth: 1 },
1613
+ "border-r": { borderRightWidth: 1 },
1614
+ "border-b": { borderBottomWidth: 1 },
1615
+ "border-l": { borderLeftWidth: 1 },
1616
+ // Cursor (web-only)
1617
+ "cursor-auto": web ? { cursor: "auto" } : null,
1618
+ "cursor-default": web ? { cursor: "default" } : null,
1619
+ "cursor-pointer": web ? { cursor: "pointer" } : null,
1620
+ "cursor-wait": web ? { cursor: "wait" } : null,
1621
+ "cursor-text": web ? { cursor: "text" } : null,
1622
+ "cursor-move": web ? { cursor: "move" } : null,
1623
+ "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
1624
+ // User select (web-only)
1625
+ "select-none": web ? { userSelect: "none" } : null,
1626
+ "select-text": web ? { userSelect: "text" } : null,
1627
+ "select-all": web ? { userSelect: "all" } : null,
1628
+ "select-auto": web ? { userSelect: "auto" } : null,
1629
+ // Pointer events (both)
1630
+ "pointer-events-none": { pointerEvents: "none" },
1631
+ "pointer-events-auto": { pointerEvents: "auto" },
1632
+ // Whitespace (web-only; RN Text uses numberOfLines prop instead)
1633
+ "whitespace-normal": web ? { whiteSpace: "normal" } : null,
1634
+ "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
1635
+ "whitespace-pre": web ? { whiteSpace: "pre" } : null,
1636
+ "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
1637
+ "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
1638
+ // Word break (web-only)
1639
+ "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
1640
+ "break-words": web ? { overflowWrap: "break-word" } : null,
1641
+ "break-all": web ? { wordBreak: "break-all" } : null,
1642
+ // Misc web-only
1643
+ truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
1644
+ "box-border": web ? { boxSizing: "border-box" } : null,
1645
+ "box-content": web ? { boxSizing: "content-box" } : null,
1646
+ "appearance-none": web ? { appearance: "none" } : null,
1647
+ "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
1648
+ outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
1649
+ resize: web ? { resize: "both" } : null,
1650
+ "resize-none": web ? { resize: "none" } : null,
1651
+ "resize-y": web ? { resize: "vertical" } : null,
1652
+ "resize-x": web ? { resize: "horizontal" } : null,
1653
+ antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
1654
+ "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
1655
+ "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
1656
+ "sr-only": web ? {
1657
+ position: "absolute",
1658
+ width: 1,
1659
+ height: 1,
1660
+ padding: 0,
1661
+ margin: -1,
1662
+ overflow: "hidden",
1663
+ clip: "rect(0,0,0,0)",
1664
+ whiteSpace: "nowrap",
1665
+ border: 0
1666
+ } : null,
1667
+ "not-sr-only": web ? {
1668
+ position: "static",
1669
+ width: "auto",
1670
+ height: "auto",
1671
+ padding: 0,
1672
+ margin: 0,
1673
+ overflow: "visible",
1674
+ clip: "auto",
1675
+ whiteSpace: "normal"
1676
+ } : null,
1677
+ // Lists (web-only)
1678
+ "list-none": web ? { listStyleType: "none" } : null,
1679
+ "list-disc": web ? { listStyleType: "disc" } : null,
1680
+ "list-decimal": web ? { listStyleType: "decimal" } : null,
1681
+ // Background clip (web-only)
1682
+ "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
1683
+ "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
1684
+ "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
1685
+ "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
1686
+ // Background image
1687
+ "bg-none": web ? { backgroundImage: "none" } : null,
1688
+ // Background size (web-only)
1689
+ "bg-auto": web ? { backgroundSize: "auto" } : null,
1690
+ "bg-cover": web ? { backgroundSize: "cover" } : null,
1691
+ "bg-contain": web ? { backgroundSize: "contain" } : null,
1692
+ // Background position (web-only)
1693
+ "bg-center": web ? { backgroundPosition: "center" } : null,
1694
+ "bg-top": web ? { backgroundPosition: "top" } : null,
1695
+ "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
1696
+ "bg-left": web ? { backgroundPosition: "left" } : null,
1697
+ "bg-right": web ? { backgroundPosition: "right" } : null,
1698
+ "bg-left-top": web ? { backgroundPosition: "left top" } : null,
1699
+ "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
1700
+ "bg-right-top": web ? { backgroundPosition: "right top" } : null,
1701
+ "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
1702
+ // Background repeat (web-only)
1703
+ "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
1704
+ "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
1705
+ "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
1706
+ "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
1707
+ "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
1708
+ "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
1709
+ // Background attachment (web-only)
1710
+ "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
1711
+ "bg-local": web ? { backgroundAttachment: "local" } : null,
1712
+ "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
1713
+ // Object position (web-only)
1714
+ "object-center": web ? { objectPosition: "center" } : null,
1715
+ "object-top": web ? { objectPosition: "top" } : null,
1716
+ "object-bottom": web ? { objectPosition: "bottom" } : null,
1717
+ "object-left": web ? { objectPosition: "left" } : null,
1718
+ "object-right": web ? { objectPosition: "right" } : null,
1719
+ "object-left-top": web ? { objectPosition: "left top" } : null,
1720
+ "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1721
+ "object-right-top": web ? { objectPosition: "right top" } : null,
1722
+ "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1723
+ // Table (web-only)
1724
+ table: web ? { display: "table" } : null,
1725
+ "table-auto": web ? { tableLayout: "auto" } : null,
1726
+ "table-fixed": web ? { tableLayout: "fixed" } : null,
1727
+ "caption-top": web ? { captionSide: "top" } : null,
1728
+ "caption-bottom": web ? { captionSide: "bottom" } : null,
1729
+ "border-collapse": web ? { borderCollapse: "collapse" } : null,
1730
+ "border-separate": web ? { borderCollapse: "separate" } : null,
1731
+ // List style position (web-only)
1732
+ "list-inside": web ? { listStylePosition: "inside" } : null,
1733
+ "list-outside": web ? { listStylePosition: "outside" } : null,
1734
+ // Font variant numeric (web-only)
1735
+ "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1736
+ ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1737
+ "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1738
+ "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1739
+ "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1740
+ "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1741
+ "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1742
+ "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1743
+ "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1744
+ // Isolation (web-only)
1745
+ isolate: web ? { isolation: "isolate" } : null,
1746
+ "isolation-auto": web ? { isolation: "auto" } : null,
1747
+ // Backface visibility (both iOS and Android)
1748
+ "backface-visible": { backfaceVisibility: "visible" },
1749
+ "backface-hidden": { backfaceVisibility: "hidden" },
1750
+ // CSS filters — standalone = default/full effect (web-only)
1751
+ grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1752
+ invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1753
+ sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1754
+ // Divide none
1755
+ "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1756
+ // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1757
+ group: web ? {} : null,
1758
+ peer: web ? {} : null,
1759
+ // Text-wrap (web-only)
1760
+ "text-wrap": web ? { textWrap: "wrap" } : null,
1761
+ "text-nowrap": web ? { textWrap: "nowrap" } : null,
1762
+ "text-balance": web ? { textWrap: "balance" } : null,
1763
+ "text-pretty": web ? { textWrap: "pretty" } : null,
1764
+ // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1765
+ // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1766
+ // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1767
+ // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1768
+ // is unchanged since there's no dynamic chrome to account for.
1769
+ // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1770
+ "w-screen": web ? { width: "100dvw" } : null,
1771
+ "min-h-screen": { minHeight: "100dvh" },
1772
+ "max-h-screen": { maxHeight: "100dvh" },
1773
+ "min-w-screen": web ? { minWidth: "100dvw" } : null,
1774
+ "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1775
+ // max-w named container sizes (mirrors Tailwind's container scale)
1776
+ "max-w-none": { maxWidth: "none" },
1777
+ "max-w-xs": { maxWidth: 320 },
1778
+ "max-w-sm": { maxWidth: 384 },
1779
+ "max-w-md": { maxWidth: 448 },
1780
+ "max-w-lg": { maxWidth: 512 },
1781
+ "max-w-xl": { maxWidth: 576 },
1782
+ "max-w-2xl": { maxWidth: 672 },
1783
+ "max-w-3xl": { maxWidth: 768 },
1784
+ "max-w-4xl": { maxWidth: 896 },
1785
+ "max-w-5xl": { maxWidth: 1024 },
1786
+ "max-w-6xl": { maxWidth: 1152 },
1787
+ "max-w-7xl": { maxWidth: 1280 },
1788
+ "max-w-prose": web ? { maxWidth: "65ch" } : null,
1789
+ // Extended cursors (web-only)
1790
+ "cursor-grab": web ? { cursor: "grab" } : null,
1791
+ "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1792
+ "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1793
+ "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1794
+ "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1795
+ "cursor-help": web ? { cursor: "help" } : null,
1796
+ "cursor-none": web ? { cursor: "none" } : null,
1797
+ // Overflow clip (web-only)
1798
+ "overflow-clip": web ? { overflow: "clip" } : null,
1799
+ "overflow-x-clip": web ? { overflowX: "clip" } : null,
1800
+ "overflow-y-clip": web ? { overflowY: "clip" } : null,
1801
+ // Scroll behavior (web-only)
1802
+ "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1803
+ "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1804
+ // Float (web-only)
1805
+ "float-left": web ? { float: "left" } : null,
1806
+ "float-right": web ? { float: "right" } : null,
1807
+ "float-start": web ? { float: "inline-start" } : null,
1808
+ "float-end": web ? { float: "inline-end" } : null,
1809
+ "float-none": web ? { float: "none" } : null,
1810
+ // Clear (web-only)
1811
+ "clear-left": web ? { clear: "left" } : null,
1812
+ "clear-right": web ? { clear: "right" } : null,
1813
+ "clear-both": web ? { clear: "both" } : null,
1814
+ "clear-start": web ? { clear: "inline-start" } : null,
1815
+ "clear-end": web ? { clear: "inline-end" } : null,
1816
+ "clear-none": web ? { clear: "none" } : null,
1817
+ // Vertical align (web-only)
1818
+ "align-baseline": web ? { verticalAlign: "baseline" } : null,
1819
+ "align-top": web ? { verticalAlign: "top" } : null,
1820
+ "align-middle": web ? { verticalAlign: "middle" } : null,
1821
+ "align-bottom": web ? { verticalAlign: "bottom" } : null,
1822
+ "align-text-top": web ? { verticalAlign: "text-top" } : null,
1823
+ "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1824
+ "align-sub": web ? { verticalAlign: "sub" } : null,
1825
+ "align-super": web ? { verticalAlign: "super" } : null,
1826
+ // Touch action (web-only)
1827
+ "touch-auto": web ? { touchAction: "auto" } : null,
1828
+ "touch-none": web ? { touchAction: "none" } : null,
1829
+ "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1830
+ "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1831
+ "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1832
+ "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1833
+ "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1834
+ "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1835
+ "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1836
+ "touch-manipulation": web ? { touchAction: "manipulation" } : null
1837
+ };
1838
+ }
1839
+ function getStandalone() {
1840
+ if (!_standalone) _standalone = buildStandalone();
1841
+ return _standalone;
1842
+ }
1843
+ var layoutResolvers = {
1844
+ // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1845
+ grid: ({ value, isArbitrary }) => {
1846
+ if (!value) return null;
1847
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1848
+ const n = parseInt(value, 10);
1849
+ if (isNaN(n) || n < 1 || n > 12) return null;
1850
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1956
1851
  },
1957
- // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1958
- // These return special __divide* markers that resolver.ts converts to
1959
- // .cls > * + * { border-... } CSS rules. No inline style is applied.
1960
- "divide-x": ({ value, isArbitrary }) => {
1961
- if (!getEffectiveIsWeb()) return null;
1962
- if (value === "reverse") return null;
1963
- if (!value) return { __divideX: 1 };
1964
- const n = parseFloat(value);
1965
- return isNaN(n) ? null : { __divideX: n };
1966
- },
1967
- "divide-y": ({ value, isArbitrary }) => {
1968
- if (!getEffectiveIsWeb()) return null;
1969
- if (value === "reverse") return null;
1970
- if (!value) return { __divideY: 1 };
1971
- const n = parseFloat(value);
1972
- return isNaN(n) ? null : { __divideY: n };
1973
- },
1974
- divide: ({ value, isArbitrary }, { colors }) => {
1975
- if (!getEffectiveIsWeb()) return null;
1976
- if (isArbitrary) return { __divideColor: value };
1977
- const styleTokens = {
1978
- solid: "solid",
1979
- dashed: "dashed",
1980
- dotted: "dotted",
1981
- double: "double",
1982
- none: "none"
1983
- };
1984
- if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1985
- const color = resolveColor(value, colors, false);
1986
- return color ? { __divideColor: color } : null;
1987
- },
1988
- // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
1989
- // Named presets give cross-platform shadow; arbitrary is web-only.
1990
- "text-shadow": ({ value, isArbitrary }, { colors }) => {
1991
- if (value === "none") {
1992
- return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
1993
- }
1994
- const webPresets = {
1995
- "": "0 2px 4px rgba(0,0,0,0.3)",
1996
- sm: "0 1px 2px rgba(0,0,0,0.3)",
1997
- md: "0 4px 6px rgba(0,0,0,0.3)",
1998
- lg: "0 8px 16px rgba(0,0,0,0.5)",
1999
- xl: "0 16px 32px rgba(0,0,0,0.5)"
2000
- };
2001
- const nativePresets = {
2002
- "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2003
- sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2004
- md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2005
- lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2006
- xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2007
- };
2008
- if (value in webPresets) {
2009
- if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2010
- return nativePresets[value] ?? null;
2011
- }
2012
- if (isArbitrary) {
2013
- return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2014
- }
2015
- const color = resolveColor(value, colors, false);
2016
- if (color) {
2017
- return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2018
- }
2019
- return null;
2020
- },
2021
- // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2022
- perspective: ({ value, isArbitrary }) => {
2023
- if (isArbitrary) {
2024
- const numPx = parseFloat(value);
2025
- if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2026
- return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2027
- }
2028
- if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2029
- const n = parseFloat(value);
2030
- if (isNaN(n)) return null;
2031
- return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2032
- },
2033
- // ── Tint color (native-only, for Image and icon components) ──────────────
2034
- tint: ({ value, isArbitrary }, { colors }) => {
2035
- if (getEffectiveIsWeb()) return null;
2036
- const color = resolveColor(value, colors, isArbitrary);
2037
- return color ? { tintColor: color } : null;
2038
- },
2039
- // ── Size shorthand (sets width AND height in one utility) ────────────────
2040
- size: ({ value, isArbitrary }, { spacing }) => {
2041
- const v = resolveSizing(value, spacing, isArbitrary);
2042
- return v !== null ? { width: v, height: v } : null;
2043
- },
2044
- // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2045
- // Each variant injects a @keyframes rule once via the __keyframe marker.
2046
- // resolver.ts detects __keyframe, injects the rule, then strips the marker
2047
- // so it never reaches element inline styles.
2048
- animate: ({ value, isArbitrary }, theme) => {
1852
+ // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1853
+ "grid-cols": ({ value, isArbitrary }) => {
2049
1854
  if (!getEffectiveIsWeb()) return null;
2050
- if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2051
- if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2052
- const custom = theme.animation?.[value];
2053
- return custom ? buildAnimationValue(custom, theme.keyframes) : null;
1855
+ if (!value) return null;
1856
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1857
+ if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1858
+ const n = parseInt(value, 10);
1859
+ if (isNaN(n) || n < 1 || n > 12) return null;
1860
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
2054
1861
  },
2055
- // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2056
- "line-clamp": ({ value }) => {
1862
+ "grid-rows": ({ value, isArbitrary }) => {
2057
1863
  if (!getEffectiveIsWeb()) return null;
2058
- if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
1864
+ if (!value) return null;
1865
+ if (isArbitrary) return { gridTemplateRows: value };
1866
+ if (value === "none") return { gridTemplateRows: "none" };
2059
1867
  const n = parseInt(value, 10);
2060
1868
  if (isNaN(n) || n < 1) return null;
2061
- return {
2062
- overflow: "hidden",
2063
- display: "-webkit-box",
2064
- WebkitBoxOrient: "vertical",
2065
- WebkitLineClamp: n
2066
- };
1869
+ return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
2067
1870
  },
2068
- // ── Caret color (web-only) ────────────────────────────────────────────────
2069
- caret: ({ value, isArbitrary }, { colors }) => {
1871
+ // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1872
+ "grid-flow": ({ value }) => {
2070
1873
  if (!getEffectiveIsWeb()) return null;
2071
- if (value === "auto" || value === "transparent") return { caretColor: value };
2072
- const color = resolveColor(value, colors, isArbitrary);
2073
- return color ? { caretColor: color } : null;
1874
+ const flows = {
1875
+ row: "row",
1876
+ col: "column",
1877
+ dense: "dense",
1878
+ "row-dense": "row dense",
1879
+ "col-dense": "column dense"
1880
+ };
1881
+ const flow = flows[value];
1882
+ return flow ? { gridAutoFlow: flow } : null;
2074
1883
  },
2075
- // ── Accent color (web-only) ───────────────────────────────────────────────
2076
- accent: ({ value, isArbitrary }, { colors }) => {
1884
+ // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1885
+ "auto-cols": ({ value, isArbitrary }) => {
2077
1886
  if (!getEffectiveIsWeb()) return null;
2078
- if (value === "auto") return { accentColor: "auto" };
2079
- const color = resolveColor(value, colors, isArbitrary);
2080
- return color ? { accentColor: color } : null;
1887
+ if (isArbitrary) return { gridAutoColumns: value };
1888
+ const presets = {
1889
+ auto: "auto",
1890
+ min: "min-content",
1891
+ max: "max-content",
1892
+ fr: "minmax(0, 1fr)"
1893
+ };
1894
+ const v = presets[value];
1895
+ return v ? { gridAutoColumns: v } : null;
2081
1896
  },
2082
- // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2083
- // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2084
- // take stroke/fill as component PROPS, not style entries, so there's no
2085
- // reliable way to apply a resolved color through the `style` prop there.
2086
- // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2087
- // color-first-then-numeric-fallback disambiguation `border` uses above.
2088
- stroke: ({ value, isArbitrary }, { colors }) => {
1897
+ "auto-rows": ({ value, isArbitrary }) => {
2089
1898
  if (!getEffectiveIsWeb()) return null;
2090
- if (!value) return null;
2091
- if (value === "none") return { stroke: "none" };
2092
- if (isArbitrary) {
2093
- const w = toNativeValue(value);
2094
- return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2095
- }
2096
- const color = resolveColor(value, colors, false);
2097
- if (color) return { stroke: color };
2098
- const n = parseFloat(value);
2099
- return isNaN(n) ? null : { strokeWidth: n };
1899
+ if (isArbitrary) return { gridAutoRows: value };
1900
+ const presets = {
1901
+ auto: "auto",
1902
+ min: "min-content",
1903
+ max: "max-content",
1904
+ fr: "minmax(0, 1fr)"
1905
+ };
1906
+ const v = presets[value];
1907
+ return v ? { gridAutoRows: v } : null;
2100
1908
  },
2101
- fill: ({ value, isArbitrary }, { colors }) => {
1909
+ // ── Grid column placement (web-only) ──────────────────────────────────────
1910
+ "col-span": ({ value }) => {
2102
1911
  if (!getEffectiveIsWeb()) return null;
2103
- if (!value) return null;
2104
- if (value === "none") return { fill: "none" };
2105
- if (isArbitrary) return { fill: value };
2106
- const color = resolveColor(value, colors, false);
2107
- return color ? { fill: color } : null;
1912
+ if (value === "full") return { gridColumn: "1 / -1" };
1913
+ const n = parseInt(value, 10);
1914
+ return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
2108
1915
  },
2109
- // ── Flex ───────────────────────────────────────────────────────────────────
2110
- flex: ({ value, isArbitrary }, { flex }) => {
2111
- if (!value) return { display: "flex" };
2112
- if (isArbitrary) {
2113
- const v = parseFloat(value);
2114
- return isNaN(v) ? null : { flex: v };
2115
- }
2116
- if (value in flex) {
2117
- const v = flex[value];
2118
- if (typeof v === "string" && !getEffectiveIsWeb()) {
2119
- if (v === "auto" || v === "initial") return { flex: 1 };
2120
- if (v === "none") return { flex: 0 };
2121
- return null;
2122
- }
2123
- return { flex: v };
2124
- }
2125
- const n = parseFloat(value);
2126
- return isNaN(n) ? null : { flex: n };
1916
+ "col-start": ({ value, isArbitrary }) => {
1917
+ if (!getEffectiveIsWeb()) return null;
1918
+ if (value === "auto") return { gridColumnStart: "auto" };
1919
+ if (isArbitrary) return { gridColumnStart: value };
1920
+ const n = parseInt(value, 10);
1921
+ return isNaN(n) ? null : { gridColumnStart: n };
2127
1922
  },
2128
- basis: ({ value, isArbitrary }, { spacing }) => {
2129
- const v = resolveSizing(value, spacing, isArbitrary);
2130
- return v !== null ? { flexBasis: v } : null;
1923
+ "col-end": ({ value, isArbitrary }) => {
1924
+ if (!getEffectiveIsWeb()) return null;
1925
+ if (value === "auto") return { gridColumnEnd: "auto" };
1926
+ if (isArbitrary) return { gridColumnEnd: value };
1927
+ const n = parseInt(value, 10);
1928
+ return isNaN(n) ? null : { gridColumnEnd: n };
2131
1929
  },
2132
- grow: ({ value }, _) => {
2133
- if (!value) return { flexGrow: 1 };
2134
- const n = parseFloat(value);
2135
- return { flexGrow: isNaN(n) ? 1 : n };
1930
+ col: ({ value, isArbitrary }) => {
1931
+ if (!getEffectiveIsWeb()) return null;
1932
+ if (!value || value === "auto") return { gridColumn: "auto" };
1933
+ if (isArbitrary) return { gridColumn: value };
1934
+ return null;
2136
1935
  },
2137
- shrink: ({ value }, _) => {
2138
- if (!value) return { flexShrink: 1 };
2139
- const n = parseFloat(value);
2140
- return { flexShrink: isNaN(n) ? 1 : n };
1936
+ // ── Grid row placement (web-only) ─────────────────────────────────────────
1937
+ "row-span": ({ value }) => {
1938
+ if (!getEffectiveIsWeb()) return null;
1939
+ if (value === "full") return { gridRow: "1 / -1" };
1940
+ const n = parseInt(value, 10);
1941
+ return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
2141
1942
  },
2142
- order: ({ value, negative }, _) => {
1943
+ "row-start": ({ value, isArbitrary }) => {
1944
+ if (!getEffectiveIsWeb()) return null;
1945
+ if (value === "auto") return { gridRowStart: "auto" };
1946
+ if (isArbitrary) return { gridRowStart: value };
2143
1947
  const n = parseInt(value, 10);
2144
- return isNaN(n) ? null : { order: negative ? -n : n };
1948
+ return isNaN(n) ? null : { gridRowStart: n };
2145
1949
  },
2146
- // ── Gap ────────────────────────────────────────────────────────────────────
2147
- gap: ({ value, negative, isArbitrary }, { spacing }) => {
2148
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2149
- return v !== null ? { gap: v } : null;
1950
+ "row-end": ({ value, isArbitrary }) => {
1951
+ if (!getEffectiveIsWeb()) return null;
1952
+ if (value === "auto") return { gridRowEnd: "auto" };
1953
+ if (isArbitrary) return { gridRowEnd: value };
1954
+ const n = parseInt(value, 10);
1955
+ return isNaN(n) ? null : { gridRowEnd: n };
2150
1956
  },
2151
- "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
2152
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2153
- return v !== null ? { columnGap: v } : null;
1957
+ row: ({ value, isArbitrary }) => {
1958
+ if (!getEffectiveIsWeb()) return null;
1959
+ if (!value || value === "auto") return { gridRow: "auto" };
1960
+ if (isArbitrary) return { gridRow: value };
1961
+ return null;
2154
1962
  },
2155
- "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
2156
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2157
- return v !== null ? { rowGap: v } : null;
1963
+ // ── Grid alignment (web-only) ──────────────────────────────────────────────
1964
+ // place-items is shorthand for align-items + justify-items on a grid container
1965
+ "place-items": ({ value }) => {
1966
+ if (!getEffectiveIsWeb()) return null;
1967
+ const map = {
1968
+ start: "start",
1969
+ end: "end",
1970
+ center: "center",
1971
+ stretch: "stretch",
1972
+ baseline: "baseline"
1973
+ };
1974
+ return map[value] ? { placeItems: map[value] } : null;
2158
1975
  },
2159
- // ── Position ───────────────────────────────────────────────────────────────
2160
- top: ({ value, negative, isArbitrary }, { spacing }) => {
2161
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2162
- return v !== null ? { top: v } : null;
1976
+ // place-content is shorthand for align-content + justify-content on a grid container
1977
+ "place-content": ({ value }) => {
1978
+ if (!getEffectiveIsWeb()) return null;
1979
+ const map = {
1980
+ start: "start",
1981
+ end: "end",
1982
+ center: "center",
1983
+ stretch: "stretch",
1984
+ between: "space-between",
1985
+ around: "space-around",
1986
+ evenly: "space-evenly",
1987
+ baseline: "baseline"
1988
+ };
1989
+ return map[value] ? { placeContent: map[value] } : null;
2163
1990
  },
2164
- right: ({ value, negative, isArbitrary }, { spacing }) => {
2165
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2166
- return v !== null ? { right: v } : null;
1991
+ // justify-items controls inline-axis alignment of grid items within their cells
1992
+ "justify-items": ({ value }) => {
1993
+ if (!getEffectiveIsWeb()) return null;
1994
+ const map = {
1995
+ start: "start",
1996
+ end: "end",
1997
+ center: "center",
1998
+ stretch: "stretch"
1999
+ };
2000
+ return map[value] ? { justifyItems: map[value] } : null;
2167
2001
  },
2168
- bottom: ({ value, negative, isArbitrary }, { spacing }) => {
2169
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2170
- return v !== null ? { bottom: v } : null;
2002
+ // place-self is shorthand for align-self + justify-self on a grid item
2003
+ "place-self": ({ value }) => {
2004
+ if (!getEffectiveIsWeb()) return null;
2005
+ const map = {
2006
+ auto: "auto",
2007
+ start: "start",
2008
+ end: "end",
2009
+ center: "center",
2010
+ stretch: "stretch"
2011
+ };
2012
+ return map[value] ? { placeSelf: map[value] } : null;
2171
2013
  },
2172
- left: ({ value, negative, isArbitrary }, { spacing }) => {
2173
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2174
- return v !== null ? { left: v } : null;
2014
+ // justify-self controls inline-axis self-alignment of a grid item
2015
+ "justify-self": ({ value }) => {
2016
+ if (!getEffectiveIsWeb()) return null;
2017
+ const map = {
2018
+ auto: "auto",
2019
+ start: "start",
2020
+ end: "end",
2021
+ center: "center",
2022
+ stretch: "stretch"
2023
+ };
2024
+ return map[value] ? { justifySelf: map[value] } : null;
2175
2025
  },
2176
- inset: ({ value, negative, isArbitrary }, { spacing }) => {
2177
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2178
- return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
2026
+ // ── Flex ───────────────────────────────────────────────────────────────────
2027
+ flex: ({ value, isArbitrary }, { flex }) => {
2028
+ if (!value) return { display: "flex" };
2029
+ if (isArbitrary) {
2030
+ const v = parseFloat(value);
2031
+ return isNaN(v) ? null : { flex: v };
2032
+ }
2033
+ if (value in flex) {
2034
+ const v = flex[value];
2035
+ if (typeof v === "string" && !getEffectiveIsWeb()) {
2036
+ if (v === "auto" || v === "initial") return { flex: 1 };
2037
+ if (v === "none") return { flex: 0 };
2038
+ return null;
2039
+ }
2040
+ return { flex: v };
2041
+ }
2042
+ const n = parseFloat(value);
2043
+ return isNaN(n) ? null : { flex: n };
2179
2044
  },
2180
- "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
2181
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2182
- return v !== null ? { left: v, right: v } : null;
2045
+ grow: ({ value }, _) => {
2046
+ if (!value) return { flexGrow: 1 };
2047
+ const n = parseFloat(value);
2048
+ return { flexGrow: isNaN(n) ? 1 : n };
2183
2049
  },
2184
- "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
2185
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2186
- return v !== null ? { top: v, bottom: v } : null;
2050
+ shrink: ({ value }, _) => {
2051
+ if (!value) return { flexShrink: 1 };
2052
+ const n = parseFloat(value);
2053
+ return { flexShrink: isNaN(n) ? 1 : n };
2054
+ },
2055
+ order: ({ value, negative }, _) => {
2056
+ const n = parseInt(value, 10);
2057
+ return isNaN(n) ? null : { order: negative ? -n : n };
2187
2058
  },
2188
2059
  // ── Z-index ────────────────────────────────────────────────────────────────
2189
2060
  z: ({ value, isArbitrary }, { zIndex }) => {
@@ -2199,16 +2070,118 @@ var RESOLVERS = {
2199
2070
  const n = parseInt(value);
2200
2071
  return isNaN(n) ? null : { zIndex: n };
2201
2072
  },
2202
- // ── Opacity ────────────────────────────────────────────────────────────────
2203
- opacity: ({ value, isArbitrary }, { opacity }) => {
2073
+ // ── Aspect ratio ───────────────────────────────────────────────────────────
2074
+ aspect: ({ value, isArbitrary }) => {
2075
+ if (isArbitrary) return { aspectRatio: value };
2076
+ const presets = { auto: "auto", square: 1, video: 16 / 9 };
2077
+ if (!(value in presets)) return null;
2078
+ const v = presets[value];
2079
+ if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
2080
+ return { aspectRatio: v };
2081
+ },
2082
+ // ── Columns (web-only) ─────────────────────────────────────────────────────
2083
+ columns: ({ value, isArbitrary }) => {
2084
+ if (!getEffectiveIsWeb()) return null;
2085
+ if (isArbitrary) return { columnCount: value };
2086
+ if (value === "auto") return { columnCount: "auto" };
2087
+ const n = parseInt(value, 10);
2088
+ if (!isNaN(n)) return { columnCount: n };
2089
+ const widths = {
2090
+ "3xs": "16rem",
2091
+ "2xs": "18rem",
2092
+ xs: "20rem",
2093
+ sm: "24rem",
2094
+ md: "28rem",
2095
+ lg: "32rem",
2096
+ xl: "36rem",
2097
+ "2xl": "42rem",
2098
+ "3xl": "48rem",
2099
+ "4xl": "56rem",
2100
+ "5xl": "64rem",
2101
+ "6xl": "72rem",
2102
+ "7xl": "80rem"
2103
+ };
2104
+ return widths[value] ? { columnWidth: widths[value] } : null;
2105
+ }
2106
+ };
2107
+
2108
+ // src/core/resolvers/typography.ts
2109
+ function resolveFontSize(value, fontSizes, isArbitrary) {
2110
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
2111
+ return fontSizes[value] ?? null;
2112
+ }
2113
+ var typographyResolvers = {
2114
+ // ── Text ───────────────────────────────────────────────────────────────────
2115
+ text: ({ value, isArbitrary }, { colors, fontSize }) => {
2116
+ const size = resolveFontSize(value, fontSize, false);
2117
+ if (!isArbitrary && size !== null) return { fontSize: size };
2204
2118
  if (isArbitrary) {
2205
- const v2 = parseFloat(value);
2206
- return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2119
+ if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
2120
+ return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
2121
+ }
2122
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
2207
2123
  }
2208
- const v = opacity[value];
2209
- if (v !== void 0) return { opacity: v };
2124
+ const color = resolveColor(value, colors, false);
2125
+ if (!color) return null;
2126
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
2127
+ },
2128
+ "text-opacity": ({ value, isArbitrary }, _) => {
2129
+ if (!getEffectiveIsWeb()) return null;
2210
2130
  const n = parseFloat(value);
2211
- return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2131
+ if (isNaN(n)) return null;
2132
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
2133
+ return { "--text-opacity": v };
2134
+ },
2135
+ // ── Text decoration advanced (web-only) ───────────────────────────────────
2136
+ decoration: ({ value, isArbitrary }, { colors }) => {
2137
+ if (!getEffectiveIsWeb()) return null;
2138
+ if (isArbitrary) {
2139
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
2140
+ return { textDecorationColor: value };
2141
+ }
2142
+ const thickTokens = {
2143
+ auto: "auto",
2144
+ "from-font": "from-font",
2145
+ "0": "0px",
2146
+ "1": "1px",
2147
+ "2": "2px",
2148
+ "4": "4px",
2149
+ "8": "8px"
2150
+ };
2151
+ if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
2152
+ const styleTokens = {
2153
+ solid: "solid",
2154
+ dashed: "dashed",
2155
+ dotted: "dotted",
2156
+ double: "double",
2157
+ wavy: "wavy"
2158
+ };
2159
+ if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
2160
+ const color = resolveColor(value, colors, false);
2161
+ return color ? { textDecorationColor: color } : null;
2162
+ },
2163
+ "underline-offset": ({ value, isArbitrary }) => {
2164
+ if (!getEffectiveIsWeb()) return null;
2165
+ if (isArbitrary) return { textUnderlineOffset: value };
2166
+ const offsets = {
2167
+ auto: "auto",
2168
+ "0": "0px",
2169
+ "1": "1px",
2170
+ "2": "2px",
2171
+ "4": "4px",
2172
+ "8": "8px"
2173
+ };
2174
+ return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2175
+ },
2176
+ // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
2177
+ // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
2178
+ // the literal string 'you can see it' quotes included), so it passes straight through as a
2179
+ // valid `content: '*'` CSS value with no extra wrapping needed.
2180
+ content: ({ value, isArbitrary }) => {
2181
+ if (!getEffectiveIsWeb()) return null;
2182
+ if (isArbitrary) return { content: value };
2183
+ const presets = { none: "none" };
2184
+ return value in presets ? { content: presets[value] } : null;
2212
2185
  },
2213
2186
  // ── Font ───────────────────────────────────────────────────────────────────
2214
2187
  font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
@@ -2234,10 +2207,57 @@ var RESOLVERS = {
2234
2207
  const v = letterSpacing[value];
2235
2208
  return v !== void 0 ? { letterSpacing: v } : null;
2236
2209
  },
2237
- // ── Shadow ─────────────────────────────────────────────────────────────────
2238
- shadow: ({ value }, { shadow }) => {
2239
- const key = value === "" ? "DEFAULT" : value;
2240
- return shadow[key] ?? null;
2210
+ // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2211
+ "line-clamp": ({ value }) => {
2212
+ if (!getEffectiveIsWeb()) return null;
2213
+ if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2214
+ const n = parseInt(value, 10);
2215
+ if (isNaN(n) || n < 1) return null;
2216
+ return {
2217
+ overflow: "hidden",
2218
+ display: "-webkit-box",
2219
+ WebkitBoxOrient: "vertical",
2220
+ WebkitLineClamp: n
2221
+ };
2222
+ }
2223
+ };
2224
+
2225
+ // src/core/resolvers/transform.ts
2226
+ var transformResolvers = {
2227
+ // ── Transform extras (web-only) ───────────────────────────────────────────
2228
+ "skew-x": ({ value, negative, isArbitrary }) => {
2229
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2230
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2231
+ return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2232
+ },
2233
+ "skew-y": ({ value, negative, isArbitrary }) => {
2234
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2235
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2236
+ return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
2237
+ },
2238
+ // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
2239
+ transform: ({ value, isArbitrary }) => {
2240
+ if (!getEffectiveIsWeb()) return null;
2241
+ if (isArbitrary) return { transform: value.replace(/_/g, " ") };
2242
+ if (value === "none") return { transform: "none" };
2243
+ return null;
2244
+ },
2245
+ // Transform origin
2246
+ origin: ({ value, isArbitrary }) => {
2247
+ if (!getEffectiveIsWeb()) return null;
2248
+ if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
2249
+ const origins = {
2250
+ center: "center",
2251
+ top: "top",
2252
+ "top-right": "top right",
2253
+ right: "right",
2254
+ "bottom-right": "bottom right",
2255
+ bottom: "bottom",
2256
+ "bottom-left": "bottom left",
2257
+ left: "left",
2258
+ "top-left": "top left"
2259
+ };
2260
+ return origins[value] ? { transformOrigin: origins[value] } : null;
2241
2261
  },
2242
2262
  // ── Scale ──────────────────────────────────────────────────────────────────
2243
2263
  // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
@@ -2268,35 +2288,86 @@ var RESOLVERS = {
2268
2288
  const finalDeg = negative ? -deg : deg;
2269
2289
  return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
2270
2290
  },
2271
- // ── Translate ──────────────────────────────────────────────────────────────
2272
- "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
2273
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2274
- if (v === null) return null;
2275
- if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
2276
- if (typeof v === "string") {
2277
- const n = parseFloat(v);
2278
- return isNaN(n) ? null : { transform: [{ translateX: n }] };
2291
+ // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2292
+ perspective: ({ value, isArbitrary }) => {
2293
+ if (isArbitrary) {
2294
+ const numPx = parseFloat(value);
2295
+ if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2296
+ return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2297
+ }
2298
+ if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2299
+ const n = parseFloat(value);
2300
+ if (isNaN(n)) return null;
2301
+ return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2302
+ }
2303
+ };
2304
+
2305
+ // src/core/resolvers/effects.ts
2306
+ function keyframeDeclToCSS(decl) {
2307
+ return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
2308
+ }
2309
+ function buildKeyframeCSS(name, steps) {
2310
+ const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
2311
+ return `@keyframes ${name} { ${body} }`;
2312
+ }
2313
+ var BUILTIN_ANIMATIONS = {
2314
+ none: { animation: "none" },
2315
+ spin: {
2316
+ animation: "kb-spin 1s linear infinite",
2317
+ __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
2318
+ },
2319
+ ping: {
2320
+ animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
2321
+ __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
2322
+ },
2323
+ pulse: {
2324
+ animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
2325
+ __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
2326
+ },
2327
+ bounce: {
2328
+ animation: "kb-bounce 1s infinite",
2329
+ __keyframe: "@keyframes kb-bounce { 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1) } 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1) } }"
2330
+ }
2331
+ };
2332
+ function buildAnimationValue(animation, keyframes) {
2333
+ const words = animation.trim().split(/\s+/);
2334
+ const name = words[0];
2335
+ if (!name) return { animation };
2336
+ const builtin = BUILTIN_ANIMATIONS[name];
2337
+ if (builtin?.__keyframe) {
2338
+ const kbName = builtin.animation.split(/\s+/, 1)[0];
2339
+ return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
2340
+ }
2341
+ const steps = keyframes?.[name];
2342
+ return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
2343
+ }
2344
+ var effectResolvers = {
2345
+ // ── Opacity ────────────────────────────────────────────────────────────────
2346
+ opacity: ({ value, isArbitrary }, { opacity }) => {
2347
+ if (isArbitrary) {
2348
+ const v2 = parseFloat(value);
2349
+ return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2279
2350
  }
2280
- return { transform: [{ translateX: v }] };
2351
+ const v = opacity[value];
2352
+ if (v !== void 0) return { opacity: v };
2353
+ const n = parseFloat(value);
2354
+ return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2281
2355
  },
2282
- "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
2283
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2284
- if (v === null) return null;
2285
- if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
2286
- if (typeof v === "string") {
2287
- const n = parseFloat(v);
2288
- return isNaN(n) ? null : { transform: [{ translateY: n }] };
2289
- }
2290
- return { transform: [{ translateY: v }] };
2356
+ // ── Shadow ─────────────────────────────────────────────────────────────────
2357
+ shadow: ({ value }, { shadow }) => {
2358
+ const key = value === "" ? "DEFAULT" : value;
2359
+ return shadow[key] ?? null;
2291
2360
  },
2292
- // ── Aspect ratio ───────────────────────────────────────────────────────────
2293
- aspect: ({ value, isArbitrary }) => {
2294
- if (isArbitrary) return { aspectRatio: value };
2295
- const presets = { auto: "auto", square: 1, video: 16 / 9 };
2296
- if (!(value in presets)) return null;
2297
- const v = presets[value];
2298
- if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
2299
- return { aspectRatio: v };
2361
+ // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2362
+ // Each variant injects a @keyframes rule once via the __keyframe marker.
2363
+ // resolver.ts detects __keyframe, injects the rule, then strips the marker
2364
+ // so it never reaches element inline styles.
2365
+ animate: ({ value, isArbitrary }, theme) => {
2366
+ if (!getEffectiveIsWeb()) return null;
2367
+ if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2368
+ if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2369
+ const custom = theme.animation?.[value];
2370
+ return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2300
2371
  },
2301
2372
  // ── Transition (web-only; use Animated API on native) ─────────────────────
2302
2373
  transition: ({ value }) => {
@@ -2324,91 +2395,107 @@ var RESOLVERS = {
2324
2395
  if (!isArbitrary && isNaN(parseFloat(value))) return null;
2325
2396
  const ms = isArbitrary ? value : `${value}ms`;
2326
2397
  return { transitionDelay: ms };
2398
+ }
2399
+ };
2400
+
2401
+ // src/core/resolvers/misc.ts
2402
+ var miscResolvers = {
2403
+ // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
2404
+ // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
2405
+ // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
2406
+ // presumably a typo) parses, which is why this only fires when the arbitrary value
2407
+ // itself contains a "property:value" pair; anything else (no colon) still resolves
2408
+ // to null exactly as before this resolver existed, rather than guessing a property.
2409
+ //
2410
+ // Only the FIRST colon splits property from value — the value itself may contain
2411
+ // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
2412
+ // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
2413
+ // custom property) key, so it can be used as the style object key as-written with no
2414
+ // extra case conversion — it only needs to look like a plausible CSS property name.
2415
+ "": ({ value, isArbitrary, negative }) => {
2416
+ if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
2417
+ if (negative) return null;
2418
+ const colonIdx = value.indexOf(":");
2419
+ if (colonIdx <= 0) return null;
2420
+ const prop = value.slice(0, colonIdx).trim();
2421
+ const cssValue = value.slice(colonIdx + 1).trim();
2422
+ if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
2423
+ return { [prop]: cssValue };
2327
2424
  },
2328
- // ── Ring ───────────────────────────────────────────────────────────────────
2329
- // Web: box-shadow outline ring (doesn't affect layout).
2330
- // Native: React Native has no box-shadow, so this falls back to borderWidth/
2331
- // borderColor — the closest visual approximation (used by other RN Tailwind-
2332
- // likes for the same reason). Unlike the web ring, this DOES affect layout,
2333
- // and it shares its properties with the `border` utility — combining
2334
- // `border-*` and `ring-*` on the same native element means whichever class
2335
- // comes later wins, since both ultimately set borderWidth/borderColor.
2336
- ring: ({ value, isArbitrary }, { colors }) => {
2337
- const onWeb = getEffectiveIsWeb();
2338
- const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
2339
- const DEFAULT_WIDTH = 3;
2340
- const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
2341
- if (!value) {
2342
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2425
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2426
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2427
+ // take stroke/fill as component PROPS, not style entries, so there's no
2428
+ // reliable way to apply a resolved color through the `style` prop there.
2429
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2430
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2431
+ stroke: ({ value, isArbitrary }, { colors }) => {
2432
+ if (!getEffectiveIsWeb()) return null;
2433
+ if (!value) return null;
2434
+ if (value === "none") return { stroke: "none" };
2435
+ if (isArbitrary) {
2436
+ const w = toNativeValue(value);
2437
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2343
2438
  }
2344
- if (value === "inset") {
2345
- return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2439
+ const color = resolveColor(value, colors, false);
2440
+ if (color) return { stroke: color };
2441
+ const n = parseFloat(value);
2442
+ return isNaN(n) ? null : { strokeWidth: n };
2443
+ },
2444
+ fill: ({ value, isArbitrary }, { colors }) => {
2445
+ if (!getEffectiveIsWeb()) return null;
2446
+ if (!value) return null;
2447
+ if (value === "none") return { fill: "none" };
2448
+ if (isArbitrary) return { fill: value };
2449
+ const color = resolveColor(value, colors, false);
2450
+ return color ? { fill: color } : null;
2451
+ },
2452
+ // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2453
+ // Named presets give cross-platform shadow; arbitrary is web-only.
2454
+ "text-shadow": ({ value, isArbitrary }, { colors }) => {
2455
+ if (value === "none") {
2456
+ return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2346
2457
  }
2347
- const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2348
- if (!isArbitrary && value in widthTokens) {
2349
- const w = widthTokens[value];
2350
- return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
2458
+ const webPresets = {
2459
+ "": "0 2px 4px rgba(0,0,0,0.3)",
2460
+ sm: "0 1px 2px rgba(0,0,0,0.3)",
2461
+ md: "0 4px 6px rgba(0,0,0,0.3)",
2462
+ lg: "0 8px 16px rgba(0,0,0,0.5)",
2463
+ xl: "0 16px 32px rgba(0,0,0,0.5)"
2464
+ };
2465
+ const nativePresets = {
2466
+ "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2467
+ sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2468
+ md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2469
+ lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2470
+ xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2471
+ };
2472
+ if (value in webPresets) {
2473
+ if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2474
+ return nativePresets[value] ?? null;
2351
2475
  }
2352
2476
  if (isArbitrary) {
2353
- const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
2354
- if (numMatch) {
2355
- const unit = numMatch[2] ?? "px";
2356
- if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
2357
- return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
2358
- }
2359
- return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
2477
+ return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2360
2478
  }
2361
2479
  const color = resolveColor(value, colors, false);
2362
2480
  if (color) {
2363
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
2481
+ return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2364
2482
  }
2365
2483
  return null;
2366
- },
2367
- // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
2368
- // create a gap between the element and the ring. There's no native
2369
- // equivalent to approximate that with (a border can't create a gap outside
2370
- // its own element without an extra wrapper view), so this stays a no-op
2371
- // on native rather than rendering a misleading half-translation.
2372
- "ring-offset": ({ value, isArbitrary }, _theme) => {
2373
- if (!getEffectiveIsWeb()) return null;
2374
- const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
2375
- const DEFAULT_RING_WIDTH = 3;
2376
- const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2377
- let offsetWidth;
2378
- if (!isArbitrary) {
2379
- offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
2380
- } else {
2381
- offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
2382
- }
2383
- if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
2384
- const isZero = offsetWidth === 0 || offsetWidth === "0";
2385
- const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
2386
- return {
2387
- boxShadow: isZero ? `0 0 0 ${DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}` : `0 0 0 ${offsetCss} #fff, 0 0 0 calc(${offsetCss} + ${DEFAULT_RING_WIDTH}px) ${DEFAULT_RING_COLOR}`
2388
- };
2389
- },
2390
- // ── Space between ─────────────────────────────────────────────────────────
2391
- // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2392
- // On native (RN 0.71+): uses columnGap/rowGap directly.
2393
- "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2394
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2395
- if (v === null) return null;
2396
- if (getEffectiveIsWeb()) return { __spaceX: v };
2397
- return { columnGap: v };
2398
- },
2399
- "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2400
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2401
- if (v === null) return null;
2402
- if (getEffectiveIsWeb()) return { __spaceY: v };
2403
- return { rowGap: v };
2404
2484
  }
2405
2485
  };
2406
- for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
2407
- RESOLVERS[utility] = makeSpacingResolver(prop);
2408
- }
2409
- for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
2410
- RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
2411
- }
2486
+
2487
+ // src/core/utilities.ts
2488
+ var RESOLVERS = {
2489
+ ...colorResolvers,
2490
+ ...spacingResolvers,
2491
+ ...borderResolvers,
2492
+ ...layoutResolvers,
2493
+ ...typographyResolvers,
2494
+ ...filterResolvers,
2495
+ ...transformResolvers,
2496
+ ...effectResolvers,
2497
+ ...miscResolvers
2498
+ };
2412
2499
  var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2413
2500
  function resolveUtility(parsed, theme) {
2414
2501
  if (!parsed.value) {
@@ -2640,85 +2727,35 @@ var BASE_RESET = [
2640
2727
  "table { border-collapse: collapse; border-spacing: 0; }"
2641
2728
  ].join("\n");
2642
2729
 
2643
- // src/core/cache.ts
2644
- var LRUCache = class {
2645
- constructor(capacity = 1e4, onEvict) {
2646
- this.capacity = capacity;
2647
- if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2648
- this.cache = /* @__PURE__ */ new Map();
2649
- this.onEvict = onEvict;
2650
- }
2651
- get(key) {
2652
- if (!this.cache.has(key)) return void 0;
2653
- const value = this.cache.get(key);
2654
- this.cache.delete(key);
2655
- this.cache.set(key, value);
2656
- return value;
2657
- }
2658
- set(key, value) {
2659
- if (this.cache.has(key)) {
2660
- this.cache.delete(key);
2661
- } else if (this.cache.size >= this.capacity) {
2662
- const oldestKey = this.cache.keys().next().value;
2663
- const oldestValue = this.cache.get(oldestKey);
2664
- this.cache.delete(oldestKey);
2665
- this.onEvict?.(oldestKey, oldestValue);
2666
- }
2667
- this.cache.set(key, value);
2668
- return this;
2669
- }
2670
- has(key) {
2671
- return this.cache.has(key);
2672
- }
2673
- delete(key) {
2674
- return this.cache.delete(key);
2675
- }
2676
- clear() {
2677
- this.cache.clear();
2678
- }
2679
- get size() {
2680
- return this.cache.size;
2681
- }
2682
- };
2683
-
2684
2730
  // src/core/responsiveStore.ts
2685
- var RESPONSIVE_KEY = "__kbach_responsive__";
2686
- function getStore() {
2687
- const g = globalThis;
2688
- if (!g[RESPONSIVE_KEY]) {
2689
- g[RESPONSIVE_KEY] = { width: 0, notifiedWidth: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
2690
- }
2691
- return g[RESPONSIVE_KEY];
2692
- }
2731
+ var store = { width: 0, notifiedWidth: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
2693
2732
  function syncGlobalWidth(width) {
2694
- getStore().width = width;
2733
+ store.width = width;
2695
2734
  }
2696
2735
  function syncGlobalScreens(screens) {
2697
- getStore().screens = screens;
2736
+ store.screens = screens;
2698
2737
  }
2699
2738
  function getGlobalScreens() {
2700
- return getStore().screens;
2739
+ return store.screens;
2701
2740
  }
2702
2741
  function setGlobalWidth(width) {
2703
- const store = getStore();
2704
2742
  store.width = width;
2705
2743
  if (store.notifiedWidth === width) return;
2706
2744
  store.notifiedWidth = width;
2707
2745
  for (const l of store.listeners) l();
2708
2746
  }
2709
2747
  function getGlobalWidth() {
2710
- return getStore().width;
2748
+ return store.width;
2711
2749
  }
2712
2750
  function subscribeGlobalWidth(listener) {
2713
- const store = getStore();
2714
2751
  store.listeners.add(listener);
2715
2752
  return () => store.listeners.delete(listener);
2716
2753
  }
2717
- function getActiveBreakpoints(width) {
2718
- const store = getStore();
2754
+ function getActiveBreakpoints(width, screens) {
2719
2755
  const w = width ?? store.width;
2756
+ const s = screens ?? store.screens;
2720
2757
  const active = /* @__PURE__ */ new Set();
2721
- for (const [name, minW] of Object.entries(store.screens)) {
2758
+ for (const [name, minW] of Object.entries(s)) {
2722
2759
  if (typeof minW === "number" && w >= minW) active.add(name);
2723
2760
  }
2724
2761
  return active;
@@ -2749,12 +2786,12 @@ function getSortedEntries(resolved) {
2749
2786
  }
2750
2787
  return sorted;
2751
2788
  }
2752
- var FONT_KEY = "__kbach_default_font__";
2789
+ var _defaultFontFamily;
2753
2790
  function setDefaultFontFamily(font) {
2754
- globalThis[FONT_KEY] = font;
2791
+ _defaultFontFamily = font;
2755
2792
  }
2756
2793
  function getDefaultFontFamily() {
2757
- return globalThis[FONT_KEY];
2794
+ return _defaultFontFamily;
2758
2795
  }
2759
2796
  var _styleEl = null;
2760
2797
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
@@ -2774,12 +2811,12 @@ function evictInjectedRule(rule) {
2774
2811
  }
2775
2812
  }
2776
2813
  var _injectedRules = new LRUCache(5e4, evictInjectedRule);
2777
- var _CSS_DISABLED_KEY = "__kbach_runtime_css_disabled__";
2814
+ var _runtimeCSSDisabled = false;
2778
2815
  function disableRuntimeCSS() {
2779
- globalThis[_CSS_DISABLED_KEY] = true;
2816
+ _runtimeCSSDisabled = true;
2780
2817
  }
2781
2818
  function isRuntimeCSSDisabled() {
2782
- return !!globalThis[_CSS_DISABLED_KEY];
2819
+ return _runtimeCSSDisabled;
2783
2820
  }
2784
2821
  function getStyleEl() {
2785
2822
  if (_styleEl) return _styleEl;
@@ -3095,19 +3132,11 @@ function deepMerge(base, override) {
3095
3132
  }
3096
3133
  return result;
3097
3134
  }
3098
- var CONFIG_KEY = "__kbach_config_store__";
3099
- function getConfigStore() {
3100
- const g = globalThis;
3101
- if (!g[CONFIG_KEY]) {
3102
- g[CONFIG_KEY] = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
3103
- }
3104
- return g[CONFIG_KEY];
3105
- }
3135
+ var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
3106
3136
  function getConfig() {
3107
- const store = getConfigStore();
3108
- if (store.resolved) return store.resolved;
3109
- store.resolved = buildConfig({});
3110
- return store.resolved;
3137
+ if (configStore.resolved) return configStore.resolved;
3138
+ configStore.resolved = buildConfig({});
3139
+ return configStore.resolved;
3111
3140
  }
3112
3141
  function resolveOneRef(ref, colors) {
3113
3142
  const lastDash = ref.lastIndexOf("-");
@@ -3182,8 +3211,7 @@ function buildConfig(userConfig) {
3182
3211
  clearPluginUtilities();
3183
3212
  clearPluginModifiers();
3184
3213
  }
3185
- const _store = getConfigStore();
3186
- for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
3214
+ for (const k of Object.keys(configStore.customVariants)) delete configStore.customVariants[k];
3187
3215
  const pluginAPI = makePluginAPI(resolved.theme);
3188
3216
  for (const plugin of resolved.plugins) {
3189
3217
  plugin(pluginAPI);
@@ -3231,78 +3259,53 @@ function makePluginAPI(theme) {
3231
3259
  };
3232
3260
  }
3233
3261
  function onConfigChange(listener) {
3234
- const store = getConfigStore();
3235
- store.listeners.add(listener);
3236
- return () => store.listeners.delete(listener);
3262
+ configStore.listeners.add(listener);
3263
+ return () => configStore.listeners.delete(listener);
3237
3264
  }
3238
3265
  function updateConfig(userConfig) {
3239
- const store = getConfigStore();
3240
- store.resolved = buildConfig(userConfig);
3241
- store._src = userConfig;
3266
+ configStore.resolved = buildConfig(userConfig);
3267
+ configStore._src = userConfig;
3242
3268
  clearCache();
3243
- for (const listener of store.listeners) {
3244
- listener(store.resolved);
3269
+ for (const listener of configStore.listeners) {
3270
+ listener(configStore.resolved);
3245
3271
  }
3246
3272
  }
3247
3273
  function initConfig(userConfig) {
3248
- if (getConfigStore()._src === userConfig) return;
3274
+ if (configStore._src === userConfig) return;
3249
3275
  updateConfig(userConfig);
3250
3276
  }
3251
- var customVariants = new Proxy(
3252
- {},
3253
- {
3254
- get(_t, p) {
3255
- return typeof p === "string" ? getConfigStore().customVariants[p] : void 0;
3256
- },
3257
- set(_t, p, v) {
3258
- if (typeof p === "string") getConfigStore().customVariants[p] = v;
3259
- return true;
3260
- },
3261
- ownKeys() {
3262
- return Object.keys(getConfigStore().customVariants);
3263
- },
3264
- getOwnPropertyDescriptor(_t, p) {
3265
- if (typeof p !== "string") return void 0;
3266
- const v = getConfigStore().customVariants[p];
3267
- return v !== void 0 ? { value: v, writable: true, enumerable: true, configurable: true } : void 0;
3268
- }
3269
- }
3270
- );
3277
+ var customVariants = configStore.customVariants;
3271
3278
 
3272
3279
  // src/core/darkModeStore.ts
3273
- var KEY = "__kbach_dark_store__";
3274
- function getStore2() {
3275
- const g = globalThis;
3276
- if (!g[KEY]) {
3277
- g[KEY] = { isDark: false, notifiedIsDark: false, subscribers: /* @__PURE__ */ new Set() };
3278
- }
3279
- return g[KEY];
3280
- }
3280
+ var store2 = { isDark: false, notifiedIsDark: false, subscribers: /* @__PURE__ */ new Set() };
3281
3281
  function syncGlobalDarkMode(isDark) {
3282
- getStore2().isDark = isDark;
3282
+ store2.isDark = isDark;
3283
3283
  }
3284
3284
  function setGlobalDarkMode(isDark) {
3285
- const store = getStore2();
3286
- store.isDark = isDark;
3287
- if (store.notifiedIsDark === isDark) return;
3288
- store.notifiedIsDark = isDark;
3289
- for (const sub of store.subscribers) sub();
3285
+ store2.isDark = isDark;
3286
+ if (store2.notifiedIsDark === isDark) return;
3287
+ store2.notifiedIsDark = isDark;
3288
+ for (const sub of store2.subscribers) sub();
3290
3289
  }
3291
3290
  function getGlobalDarkMode() {
3292
- return getStore2().isDark;
3291
+ return store2.isDark;
3293
3292
  }
3294
3293
  function subscribeGlobalDarkMode(callback) {
3295
- const store = getStore2();
3296
- store.subscribers.add(callback);
3297
- return () => store.subscribers.delete(callback);
3294
+ store2.subscribers.add(callback);
3295
+ return () => store2.subscribers.delete(callback);
3298
3296
  }
3299
3297
 
3300
3298
  // src/useSyncExternalStoreShim.ts
3301
3299
  import React from "react";
3302
- var useSyncExternalStore = React.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
3300
+ var useSyncExternalStore = React.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, getServerSnapshot) {
3301
+ const isServer = typeof window === "undefined";
3303
3302
  const [, forceUpdate] = React.useReducer((n) => n + 1, 0);
3304
- React.useEffect(() => subscribe(forceUpdate), [subscribe]);
3305
- return getSnapshot();
3303
+ const value = isServer && getServerSnapshot ? getServerSnapshot() : getSnapshot();
3304
+ React.useEffect(() => {
3305
+ if (getSnapshot() !== value) forceUpdate();
3306
+ return subscribe(forceUpdate);
3307
+ }, [subscribe]);
3308
+ return value;
3306
3309
  };
3307
3310
 
3308
3311
  // src/useGlobalDarkMode.ts
@@ -3767,15 +3770,16 @@ var InteractiveWrapper = forwardRef(
3767
3770
  if (!isWebPlatform) setFocused(false);
3768
3771
  }), [onBlur, isWebPlatform]);
3769
3772
  const { children, disabled, checked, ...restForComponent } = rest;
3773
+ const isNonStringComponent = typeof Component !== "string";
3770
3774
  const computedStyle = useMemo(
3771
3775
  () => {
3772
3776
  if (isWebPlatform) return {};
3773
3777
  const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
3774
3778
  stripInternalMarkers(s);
3775
- if (typeof Component !== "string") stripWebOnlyProps(s);
3779
+ if (isNonStringComponent) stripWebOnlyProps(s);
3776
3780
  return s;
3777
3781
  },
3778
- [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
3782
+ [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked, isNonStringComponent]
3779
3783
  );
3780
3784
  const skipComputedInline = isWebPlatform;
3781
3785
  const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;