@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.
@@ -615,14 +615,16 @@ var defaultTheme = {
615
615
  animation: {}
616
616
  };
617
617
 
618
- // src/core/utilities.ts
618
+ // src/core/resolvers/color.ts
619
619
  function resolveColor(value, colors, isArbitrary) {
620
620
  if (isArbitrary) return value;
621
621
  const slashIdx = value.indexOf("/");
622
622
  const colorPart = slashIdx > 0 ? value.slice(0, slashIdx) : value;
623
623
  const opacityPart = slashIdx > 0 ? value.slice(slashIdx + 1) : null;
624
624
  let hex = null;
625
- if (colorPart in colors) {
625
+ if (colorPart.startsWith("[") && colorPart.endsWith("]")) {
626
+ hex = colorPart.slice(1, -1);
627
+ } else if (colorPart in colors) {
626
628
  const entry = colors[colorPart];
627
629
  if (typeof entry === "string") hex = entry;
628
630
  else if (typeof entry === "object" && "6" in entry) hex = entry["6"];
@@ -687,6 +689,86 @@ function withOpacityVar(color, varName) {
687
689
  const [r, g, b] = rgb;
688
690
  return `rgba(${r},${g},${b},var(${varName},1))`;
689
691
  }
692
+ var colorResolvers = {
693
+ // ── Background ─────────────────────────────────────────────────────────────
694
+ bg: ({ value, isArbitrary }, { colors }) => {
695
+ const color = resolveColor(value, colors, isArbitrary);
696
+ if (!color) return null;
697
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
698
+ },
699
+ "bg-opacity": ({ value, isArbitrary }, _) => {
700
+ if (!getEffectiveIsWeb()) return null;
701
+ const n = parseFloat(value);
702
+ if (isNaN(n)) return null;
703
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
704
+ return { "--bg-opacity": v };
705
+ },
706
+ // ── Background gradient (CSS variable gradient stops, web-only) ──────────
707
+ "bg-gradient-to": ({ value }) => {
708
+ if (!getEffectiveIsWeb()) return null;
709
+ const directions = {
710
+ t: "to top",
711
+ tr: "to top right",
712
+ r: "to right",
713
+ br: "to bottom right",
714
+ b: "to bottom",
715
+ bl: "to bottom left",
716
+ l: "to left",
717
+ tl: "to top left"
718
+ };
719
+ const dir = directions[value];
720
+ if (!dir) return null;
721
+ return {
722
+ backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
723
+ };
724
+ },
725
+ from: ({ value, isArbitrary }, { colors }) => {
726
+ if (!getEffectiveIsWeb()) return null;
727
+ const color = resolveColor(value, colors, isArbitrary);
728
+ if (!color) return null;
729
+ return {
730
+ "--kb-gradient-from": color,
731
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
732
+ };
733
+ },
734
+ via: ({ value, isArbitrary }, { colors }) => {
735
+ if (!getEffectiveIsWeb()) return null;
736
+ const color = resolveColor(value, colors, isArbitrary);
737
+ if (!color) return null;
738
+ return {
739
+ "--kb-gradient-via": color,
740
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
741
+ };
742
+ },
743
+ to: ({ value, isArbitrary }, { colors }) => {
744
+ if (!getEffectiveIsWeb()) return null;
745
+ const color = resolveColor(value, colors, isArbitrary);
746
+ if (!color) return null;
747
+ return { "--kb-gradient-to": color };
748
+ },
749
+ // ── Tint color (native-only, for Image and icon components) ──────────────
750
+ tint: ({ value, isArbitrary }, { colors }) => {
751
+ if (getEffectiveIsWeb()) return null;
752
+ const color = resolveColor(value, colors, isArbitrary);
753
+ return color ? { tintColor: color } : null;
754
+ },
755
+ // ── Caret color (web-only) ────────────────────────────────────────────────
756
+ caret: ({ value, isArbitrary }, { colors }) => {
757
+ if (!getEffectiveIsWeb()) return null;
758
+ if (value === "auto" || value === "transparent") return { caretColor: value };
759
+ const color = resolveColor(value, colors, isArbitrary);
760
+ return color ? { caretColor: color } : null;
761
+ },
762
+ // ── Accent color (web-only) ───────────────────────────────────────────────
763
+ accent: ({ value, isArbitrary }, { colors }) => {
764
+ if (!getEffectiveIsWeb()) return null;
765
+ if (value === "auto") return { accentColor: "auto" };
766
+ const color = resolveColor(value, colors, isArbitrary);
767
+ return color ? { accentColor: color } : null;
768
+ }
769
+ };
770
+
771
+ // src/core/resolvers/spacing.ts
690
772
  function resolveSpacing(value, negative, spacing, isArbitrary) {
691
773
  if (isArbitrary) {
692
774
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -719,516 +801,174 @@ function resolveSizing(value, spacing, isArbitrary) {
719
801
  }
720
802
  return null;
721
803
  }
804
+ function makeSpacingResolver(prop) {
805
+ return ({ value, negative, isArbitrary }, { spacing }) => {
806
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
807
+ return v !== null ? { [prop]: v } : null;
808
+ };
809
+ }
810
+ var PADDING_MARGIN_PROPS = {
811
+ p: "padding",
812
+ px: "paddingHorizontal",
813
+ py: "paddingVertical",
814
+ pt: "paddingTop",
815
+ pr: "paddingRight",
816
+ pb: "paddingBottom",
817
+ pl: "paddingLeft",
818
+ m: "margin",
819
+ mx: "marginHorizontal",
820
+ my: "marginVertical",
821
+ mt: "marginTop",
822
+ mr: "marginRight",
823
+ mb: "marginBottom",
824
+ ml: "marginLeft"
825
+ };
826
+ var spacingResolvers = {
827
+ // ── Sizing ─────────────────────────────────────────────────────────────────
828
+ w: ({ value, isArbitrary }, { spacing }) => {
829
+ const v = resolveSizing(value, spacing, isArbitrary);
830
+ return v !== null ? { width: v } : null;
831
+ },
832
+ h: ({ value, isArbitrary }, { spacing }) => {
833
+ const v = resolveSizing(value, spacing, isArbitrary);
834
+ return v !== null ? { height: v } : null;
835
+ },
836
+ "min-w": ({ value, isArbitrary }, { spacing }) => {
837
+ const v = resolveSizing(value, spacing, isArbitrary);
838
+ return v !== null ? { minWidth: v } : null;
839
+ },
840
+ "min-h": ({ value, isArbitrary }, { spacing }) => {
841
+ const v = resolveSizing(value, spacing, isArbitrary);
842
+ return v !== null ? { minHeight: v } : null;
843
+ },
844
+ "max-w": ({ value, isArbitrary }, { spacing }) => {
845
+ const v = resolveSizing(value, spacing, isArbitrary);
846
+ return v !== null ? { maxWidth: v } : null;
847
+ },
848
+ "max-h": ({ value, isArbitrary }, { spacing }) => {
849
+ const v = resolveSizing(value, spacing, isArbitrary);
850
+ return v !== null ? { maxHeight: v } : null;
851
+ },
852
+ // ── Size shorthand (sets width AND height in one utility) ────────────────
853
+ size: ({ value, isArbitrary }, { spacing }) => {
854
+ const v = resolveSizing(value, spacing, isArbitrary);
855
+ return v !== null ? { width: v, height: v } : null;
856
+ },
857
+ // ── Flex basis ─────────────────────────────────────────────────────────────
858
+ basis: ({ value, isArbitrary }, { spacing }) => {
859
+ const v = resolveSizing(value, spacing, isArbitrary);
860
+ return v !== null ? { flexBasis: v } : null;
861
+ },
862
+ // ── Gap ────────────────────────────────────────────────────────────────────
863
+ gap: ({ value, negative, isArbitrary }, { spacing }) => {
864
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
865
+ return v !== null ? { gap: v } : null;
866
+ },
867
+ "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
868
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
869
+ return v !== null ? { columnGap: v } : null;
870
+ },
871
+ "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
872
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
873
+ return v !== null ? { rowGap: v } : null;
874
+ },
875
+ // ── Position ───────────────────────────────────────────────────────────────
876
+ top: ({ value, negative, isArbitrary }, { spacing }) => {
877
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
878
+ return v !== null ? { top: v } : null;
879
+ },
880
+ right: ({ value, negative, isArbitrary }, { spacing }) => {
881
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
882
+ return v !== null ? { right: v } : null;
883
+ },
884
+ bottom: ({ value, negative, isArbitrary }, { spacing }) => {
885
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
886
+ return v !== null ? { bottom: v } : null;
887
+ },
888
+ left: ({ value, negative, isArbitrary }, { spacing }) => {
889
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
890
+ return v !== null ? { left: v } : null;
891
+ },
892
+ inset: ({ value, negative, isArbitrary }, { spacing }) => {
893
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
894
+ return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
895
+ },
896
+ "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
897
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
898
+ return v !== null ? { left: v, right: v } : null;
899
+ },
900
+ "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
901
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
902
+ return v !== null ? { top: v, bottom: v } : null;
903
+ },
904
+ // ── Translate ──────────────────────────────────────────────────────────────
905
+ "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
906
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
907
+ if (v === null) return null;
908
+ if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
909
+ if (typeof v === "string") {
910
+ const n = parseFloat(v);
911
+ return isNaN(n) ? null : { transform: [{ translateX: n }] };
912
+ }
913
+ return { transform: [{ translateX: v }] };
914
+ },
915
+ "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
916
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
917
+ if (v === null) return null;
918
+ if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
919
+ if (typeof v === "string") {
920
+ const n = parseFloat(v);
921
+ return isNaN(n) ? null : { transform: [{ translateY: n }] };
922
+ }
923
+ return { transform: [{ translateY: v }] };
924
+ },
925
+ // ── Space between ─────────────────────────────────────────────────────────
926
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
927
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
928
+ "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
929
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
930
+ if (v === null) return null;
931
+ if (getEffectiveIsWeb()) return { __spaceX: v };
932
+ return { columnGap: v };
933
+ },
934
+ "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
935
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
936
+ if (v === null) return null;
937
+ if (getEffectiveIsWeb()) return { __spaceY: v };
938
+ return { rowGap: v };
939
+ }
940
+ };
941
+ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
942
+ spacingResolvers[utility] = makeSpacingResolver(prop);
943
+ }
944
+
945
+ // src/core/resolvers/border.ts
722
946
  function resolveRadius(value, radii, isArbitrary) {
723
947
  if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
724
948
  const key = value === "" ? "DEFAULT" : value;
725
949
  return radii[key] ?? null;
726
950
  }
727
- function resolveFontSize(value, fontSizes, isArbitrary) {
728
- if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
729
- return fontSizes[value] ?? null;
951
+ function makeBorderSideResolver(widthProp, colorProp) {
952
+ return ({ value, isArbitrary }, { colors, borderWidth }) => {
953
+ if (!value) return { [widthProp]: 1 };
954
+ if (isArbitrary) {
955
+ const w2 = toNativeValue(value);
956
+ if (typeof w2 === "number") return { [widthProp]: w2 };
957
+ return { [colorProp]: value };
958
+ }
959
+ const color = resolveColor(value, colors, false);
960
+ if (color) return { [colorProp]: color };
961
+ const w = borderWidth[value];
962
+ return w !== void 0 ? { [widthProp]: w } : null;
963
+ };
730
964
  }
731
- 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,)";
732
- 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,)";
733
- var _standalone = null;
734
- function buildStandalone() {
735
- const web = getEffectiveIsWeb();
736
- return {
737
- // Display
738
- // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
739
- // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
740
- // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
741
- // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
742
- // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
743
- // time, but without it these utilities couldn't re-show a `hidden` element on
744
- // native either (null → no style applied → still display:'none').
745
- flex: { display: "flex" },
746
- block: web ? { display: "block" } : null,
747
- "inline-block": { display: web ? "inline-block" : "flex" },
748
- inline: { display: web ? "inline" : "flex" },
749
- grid: web ? { display: "grid" } : null,
750
- grd: web ? { display: "grid" } : null,
751
- "inline-flex": { display: web ? "inline-flex" : "flex" },
752
- "inline-grid": { display: web ? "inline-grid" : "flex" },
753
- hidden: { display: "none" },
754
- contents: web ? { display: "contents" } : null,
755
- "flow-root": web ? { display: "flow-root" } : null,
756
- // Flex direction (both web and native)
757
- "flex-row": { flexDirection: "row" },
758
- "flex-col": { flexDirection: "column" },
759
- "flex-row-reverse": { flexDirection: "row-reverse" },
760
- "flex-col-reverse": { flexDirection: "column-reverse" },
761
- // Flex wrap (both)
762
- "flex-wrap": { flexWrap: "wrap" },
763
- "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
764
- "flex-nowrap": { flexWrap: "nowrap" },
765
- // Flex grow / shrink (both)
766
- "flex-grow": { flexGrow: 1 },
767
- "flex-grow-0": { flexGrow: 0 },
768
- "flex-shrink": { flexShrink: 1 },
769
- "flex-shrink-0": { flexShrink: 0 },
770
- // Align items (both)
771
- "items-start": { alignItems: "flex-start" },
772
- "items-end": { alignItems: "flex-end" },
773
- "items-center": { alignItems: "center" },
774
- "items-baseline": { alignItems: "baseline" },
775
- "items-stretch": { alignItems: "stretch" },
776
- // Justify content (both)
777
- "justify-start": { justifyContent: "flex-start" },
778
- "justify-end": { justifyContent: "flex-end" },
779
- "justify-center": { justifyContent: "center" },
780
- "justify-between": { justifyContent: "space-between" },
781
- "justify-around": { justifyContent: "space-around" },
782
- "justify-evenly": { justifyContent: "space-evenly" },
783
- // Align content (both)
784
- "content-start": { alignContent: "flex-start" },
785
- "content-end": { alignContent: "flex-end" },
786
- "content-center": { alignContent: "center" },
787
- "content-between": { alignContent: "space-between" },
788
- "content-around": { alignContent: "space-around" },
789
- "content-evenly": { alignContent: "space-evenly" },
790
- "content-stretch": { alignContent: "stretch" },
791
- // Align self (both)
792
- "self-auto": { alignSelf: "auto" },
793
- "self-start": { alignSelf: "flex-start" },
794
- "self-end": { alignSelf: "flex-end" },
795
- "self-center": { alignSelf: "center" },
796
- "self-stretch": { alignSelf: "stretch" },
797
- "self-baseline": { alignSelf: "baseline" },
798
- // Text align (both)
799
- "text-left": { textAlign: "left" },
800
- "text-right": { textAlign: "right" },
801
- "text-center": { textAlign: "center" },
802
- "text-justify": { textAlign: "justify" },
803
- // Font weight shortcuts (both)
804
- "font-thin": { fontWeight: "100" },
805
- "font-extralight": { fontWeight: "200" },
806
- "font-light": { fontWeight: "300" },
807
- "font-normal": { fontWeight: "400" },
808
- "font-medium": { fontWeight: "500" },
809
- "font-semibold": { fontWeight: "600" },
810
- "font-bold": { fontWeight: "700" },
811
- "font-extrabold": { fontWeight: "800" },
812
- "font-black": { fontWeight: "900" },
813
- // Text decoration (overline is web-only)
814
- underline: { textDecorationLine: "underline" },
815
- overline: web ? { textDecorationLine: "overline" } : null,
816
- "line-through": { textDecorationLine: "line-through" },
817
- "no-underline": { textDecorationLine: "none" },
818
- // Text transform (both)
819
- uppercase: { textTransform: "uppercase" },
820
- lowercase: { textTransform: "lowercase" },
821
- capitalize: { textTransform: "capitalize" },
822
- "normal-case": { textTransform: "none" },
823
- // Font style (both)
824
- italic: { fontStyle: "italic" },
825
- "non-italic": { fontStyle: "normal" },
826
- // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
827
- relative: { position: "relative" },
828
- absolute: { position: "absolute" },
829
- fixed: web ? { position: "fixed" } : null,
830
- sticky: web ? { position: "sticky" } : null,
831
- static: web ? { position: "static" } : null,
832
- // Visibility (no `visibility` prop on native; use opacity instead)
833
- visible: web ? { visibility: "visible" } : null,
834
- invisible: web ? { visibility: "hidden" } : { opacity: 0 },
835
- // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
836
- "overflow-hidden": { overflow: "hidden" },
837
- "overflow-visible": { overflow: "visible" },
838
- "overflow-scroll": { overflow: "scroll" },
839
- "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
840
- // overflowX / overflowY are web-only
841
- "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
842
- "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
843
- "overflow-x-auto": web ? { overflowX: "auto" } : null,
844
- "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
845
- "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
846
- "overflow-y-auto": web ? { overflowY: "auto" } : null,
847
- // Object fit (web-only; use resizeMode prop on RN Image)
848
- "object-contain": web ? { objectFit: "contain" } : null,
849
- "object-cover": web ? { objectFit: "cover" } : null,
850
- "object-fill": web ? { objectFit: "fill" } : null,
851
- "object-none": web ? { objectFit: "none" } : null,
852
- "object-scale-down": web ? { objectFit: "scale-down" } : null,
853
- // Border style (both)
854
- "border-solid": { borderStyle: "solid" },
855
- "border-dashed": { borderStyle: "dashed" },
856
- "border-dotted": { borderStyle: "dotted" },
857
- "border-none": { borderWidth: 0 },
858
- // Border sides default width (both)
859
- "border-t": { borderTopWidth: 1 },
860
- "border-r": { borderRightWidth: 1 },
861
- "border-b": { borderBottomWidth: 1 },
862
- "border-l": { borderLeftWidth: 1 },
863
- // Cursor (web-only)
864
- "cursor-auto": web ? { cursor: "auto" } : null,
865
- "cursor-default": web ? { cursor: "default" } : null,
866
- "cursor-pointer": web ? { cursor: "pointer" } : null,
867
- "cursor-wait": web ? { cursor: "wait" } : null,
868
- "cursor-text": web ? { cursor: "text" } : null,
869
- "cursor-move": web ? { cursor: "move" } : null,
870
- "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
871
- // User select (web-only)
872
- "select-none": web ? { userSelect: "none" } : null,
873
- "select-text": web ? { userSelect: "text" } : null,
874
- "select-all": web ? { userSelect: "all" } : null,
875
- "select-auto": web ? { userSelect: "auto" } : null,
876
- // Pointer events (both)
877
- "pointer-events-none": { pointerEvents: "none" },
878
- "pointer-events-auto": { pointerEvents: "auto" },
879
- // Whitespace (web-only; RN Text uses numberOfLines prop instead)
880
- "whitespace-normal": web ? { whiteSpace: "normal" } : null,
881
- "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
882
- "whitespace-pre": web ? { whiteSpace: "pre" } : null,
883
- "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
884
- "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
885
- // Word break (web-only)
886
- "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
887
- "break-words": web ? { overflowWrap: "break-word" } : null,
888
- "break-all": web ? { wordBreak: "break-all" } : null,
889
- // Misc web-only
890
- truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
891
- "box-border": web ? { boxSizing: "border-box" } : null,
892
- "box-content": web ? { boxSizing: "content-box" } : null,
893
- "appearance-none": web ? { appearance: "none" } : null,
894
- "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
895
- outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
896
- resize: web ? { resize: "both" } : null,
897
- "resize-none": web ? { resize: "none" } : null,
898
- "resize-y": web ? { resize: "vertical" } : null,
899
- "resize-x": web ? { resize: "horizontal" } : null,
900
- antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
901
- "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
902
- "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
903
- "sr-only": web ? {
904
- position: "absolute",
905
- width: 1,
906
- height: 1,
907
- padding: 0,
908
- margin: -1,
909
- overflow: "hidden",
910
- clip: "rect(0,0,0,0)",
911
- whiteSpace: "nowrap",
912
- border: 0
913
- } : null,
914
- "not-sr-only": web ? {
915
- position: "static",
916
- width: "auto",
917
- height: "auto",
918
- padding: 0,
919
- margin: 0,
920
- overflow: "visible",
921
- clip: "auto",
922
- whiteSpace: "normal"
923
- } : null,
924
- // Lists (web-only)
925
- "list-none": web ? { listStyleType: "none" } : null,
926
- "list-disc": web ? { listStyleType: "disc" } : null,
927
- "list-decimal": web ? { listStyleType: "decimal" } : null,
928
- // Background clip (web-only)
929
- "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
930
- "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
931
- "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
932
- "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
933
- // Background image
934
- "bg-none": web ? { backgroundImage: "none" } : null,
935
- // Background size (web-only)
936
- "bg-auto": web ? { backgroundSize: "auto" } : null,
937
- "bg-cover": web ? { backgroundSize: "cover" } : null,
938
- "bg-contain": web ? { backgroundSize: "contain" } : null,
939
- // Background position (web-only)
940
- "bg-center": web ? { backgroundPosition: "center" } : null,
941
- "bg-top": web ? { backgroundPosition: "top" } : null,
942
- "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
943
- "bg-left": web ? { backgroundPosition: "left" } : null,
944
- "bg-right": web ? { backgroundPosition: "right" } : null,
945
- "bg-left-top": web ? { backgroundPosition: "left top" } : null,
946
- "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
947
- "bg-right-top": web ? { backgroundPosition: "right top" } : null,
948
- "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
949
- // Background repeat (web-only)
950
- "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
951
- "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
952
- "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
953
- "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
954
- "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
955
- "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
956
- // Background attachment (web-only)
957
- "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
958
- "bg-local": web ? { backgroundAttachment: "local" } : null,
959
- "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
960
- // Object position (web-only)
961
- "object-center": web ? { objectPosition: "center" } : null,
962
- "object-top": web ? { objectPosition: "top" } : null,
963
- "object-bottom": web ? { objectPosition: "bottom" } : null,
964
- "object-left": web ? { objectPosition: "left" } : null,
965
- "object-right": web ? { objectPosition: "right" } : null,
966
- "object-left-top": web ? { objectPosition: "left top" } : null,
967
- "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
968
- "object-right-top": web ? { objectPosition: "right top" } : null,
969
- "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
970
- // Table (web-only)
971
- table: web ? { display: "table" } : null,
972
- "table-auto": web ? { tableLayout: "auto" } : null,
973
- "table-fixed": web ? { tableLayout: "fixed" } : null,
974
- "caption-top": web ? { captionSide: "top" } : null,
975
- "caption-bottom": web ? { captionSide: "bottom" } : null,
976
- "border-collapse": web ? { borderCollapse: "collapse" } : null,
977
- "border-separate": web ? { borderCollapse: "separate" } : null,
978
- // List style position (web-only)
979
- "list-inside": web ? { listStylePosition: "inside" } : null,
980
- "list-outside": web ? { listStylePosition: "outside" } : null,
981
- // Font variant numeric (web-only)
982
- "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
983
- ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
984
- "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
985
- "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
986
- "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
987
- "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
988
- "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
989
- "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
990
- "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
991
- // Isolation (web-only)
992
- isolate: web ? { isolation: "isolate" } : null,
993
- "isolation-auto": web ? { isolation: "auto" } : null,
994
- // Backface visibility (both iOS and Android)
995
- "backface-visible": { backfaceVisibility: "visible" },
996
- "backface-hidden": { backfaceVisibility: "hidden" },
997
- // CSS filters — standalone = default/full effect (web-only)
998
- grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
999
- invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1000
- sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1001
- // Divide none
1002
- "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1003
- // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1004
- group: web ? {} : null,
1005
- peer: web ? {} : null,
1006
- // Text-wrap (web-only)
1007
- "text-wrap": web ? { textWrap: "wrap" } : null,
1008
- "text-nowrap": web ? { textWrap: "nowrap" } : null,
1009
- "text-balance": web ? { textWrap: "balance" } : null,
1010
- "text-pretty": web ? { textWrap: "pretty" } : null,
1011
- // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1012
- // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1013
- // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1014
- // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1015
- // is unchanged since there's no dynamic chrome to account for.
1016
- // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1017
- "w-screen": web ? { width: "100dvw" } : null,
1018
- "min-h-screen": { minHeight: "100dvh" },
1019
- "max-h-screen": { maxHeight: "100dvh" },
1020
- "min-w-screen": web ? { minWidth: "100dvw" } : null,
1021
- "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1022
- // max-w named container sizes (mirrors Tailwind's container scale)
1023
- "max-w-none": { maxWidth: "none" },
1024
- "max-w-xs": { maxWidth: 320 },
1025
- "max-w-sm": { maxWidth: 384 },
1026
- "max-w-md": { maxWidth: 448 },
1027
- "max-w-lg": { maxWidth: 512 },
1028
- "max-w-xl": { maxWidth: 576 },
1029
- "max-w-2xl": { maxWidth: 672 },
1030
- "max-w-3xl": { maxWidth: 768 },
1031
- "max-w-4xl": { maxWidth: 896 },
1032
- "max-w-5xl": { maxWidth: 1024 },
1033
- "max-w-6xl": { maxWidth: 1152 },
1034
- "max-w-7xl": { maxWidth: 1280 },
1035
- "max-w-prose": web ? { maxWidth: "65ch" } : null,
1036
- // Extended cursors (web-only)
1037
- "cursor-grab": web ? { cursor: "grab" } : null,
1038
- "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1039
- "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1040
- "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1041
- "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1042
- "cursor-help": web ? { cursor: "help" } : null,
1043
- "cursor-none": web ? { cursor: "none" } : null,
1044
- // Overflow clip (web-only)
1045
- "overflow-clip": web ? { overflow: "clip" } : null,
1046
- "overflow-x-clip": web ? { overflowX: "clip" } : null,
1047
- "overflow-y-clip": web ? { overflowY: "clip" } : null,
1048
- // Scroll behavior (web-only)
1049
- "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1050
- "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1051
- // Float (web-only)
1052
- "float-left": web ? { float: "left" } : null,
1053
- "float-right": web ? { float: "right" } : null,
1054
- "float-start": web ? { float: "inline-start" } : null,
1055
- "float-end": web ? { float: "inline-end" } : null,
1056
- "float-none": web ? { float: "none" } : null,
1057
- // Clear (web-only)
1058
- "clear-left": web ? { clear: "left" } : null,
1059
- "clear-right": web ? { clear: "right" } : null,
1060
- "clear-both": web ? { clear: "both" } : null,
1061
- "clear-start": web ? { clear: "inline-start" } : null,
1062
- "clear-end": web ? { clear: "inline-end" } : null,
1063
- "clear-none": web ? { clear: "none" } : null,
1064
- // Vertical align (web-only)
1065
- "align-baseline": web ? { verticalAlign: "baseline" } : null,
1066
- "align-top": web ? { verticalAlign: "top" } : null,
1067
- "align-middle": web ? { verticalAlign: "middle" } : null,
1068
- "align-bottom": web ? { verticalAlign: "bottom" } : null,
1069
- "align-text-top": web ? { verticalAlign: "text-top" } : null,
1070
- "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1071
- "align-sub": web ? { verticalAlign: "sub" } : null,
1072
- "align-super": web ? { verticalAlign: "super" } : null,
1073
- // Touch action (web-only)
1074
- "touch-auto": web ? { touchAction: "auto" } : null,
1075
- "touch-none": web ? { touchAction: "none" } : null,
1076
- "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1077
- "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1078
- "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1079
- "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1080
- "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1081
- "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1082
- "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1083
- "touch-manipulation": web ? { touchAction: "manipulation" } : null
1084
- };
1085
- }
1086
- function getStandalone() {
1087
- if (!_standalone) _standalone = buildStandalone();
1088
- return _standalone;
1089
- }
1090
- function makeSpacingResolver(prop) {
1091
- return ({ value, negative, isArbitrary }, { spacing }) => {
1092
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
1093
- return v !== null ? { [prop]: v } : null;
1094
- };
1095
- }
1096
- var PADDING_MARGIN_PROPS = {
1097
- p: "padding",
1098
- px: "paddingHorizontal",
1099
- py: "paddingVertical",
1100
- pt: "paddingTop",
1101
- pr: "paddingRight",
1102
- pb: "paddingBottom",
1103
- pl: "paddingLeft",
1104
- m: "margin",
1105
- mx: "marginHorizontal",
1106
- my: "marginVertical",
1107
- mt: "marginTop",
1108
- mr: "marginRight",
1109
- mb: "marginBottom",
1110
- ml: "marginLeft"
1111
- };
1112
- function makeBorderSideResolver(widthProp, colorProp) {
1113
- return ({ value, isArbitrary }, { colors, borderWidth }) => {
1114
- if (!value) return { [widthProp]: 1 };
1115
- if (isArbitrary) {
1116
- const w2 = toNativeValue(value);
1117
- if (typeof w2 === "number") return { [widthProp]: w2 };
1118
- return { [colorProp]: value };
1119
- }
1120
- const color = resolveColor(value, colors, false);
1121
- if (color) return { [colorProp]: color };
1122
- const w = borderWidth[value];
1123
- return w !== void 0 ? { [widthProp]: w } : null;
1124
- };
1125
- }
1126
- var BORDER_SIDE_PROPS = {
1127
- "border-t": ["borderTopWidth", "borderTopColor"],
1128
- "border-r": ["borderRightWidth", "borderRightColor"],
1129
- "border-b": ["borderBottomWidth", "borderBottomColor"],
1130
- "border-l": ["borderLeftWidth", "borderLeftColor"]
1131
- };
1132
- function keyframeDeclToCSS(decl) {
1133
- return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
1134
- }
1135
- function buildKeyframeCSS(name, steps) {
1136
- const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
1137
- return `@keyframes ${name} { ${body} }`;
1138
- }
1139
- var BUILTIN_ANIMATIONS = {
1140
- none: { animation: "none" },
1141
- spin: {
1142
- animation: "kb-spin 1s linear infinite",
1143
- __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
1144
- },
1145
- ping: {
1146
- animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
1147
- __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
1148
- },
1149
- pulse: {
1150
- animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
1151
- __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
1152
- },
1153
- bounce: {
1154
- animation: "kb-bounce 1s infinite",
1155
- __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) } }"
1156
- }
1157
- };
1158
- function buildAnimationValue(animation, keyframes) {
1159
- const words = animation.trim().split(/\s+/);
1160
- const name = words[0];
1161
- if (!name) return { animation };
1162
- const builtin = BUILTIN_ANIMATIONS[name];
1163
- if (builtin?.__keyframe) {
1164
- const kbName = builtin.animation.split(/\s+/, 1)[0];
1165
- return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
1166
- }
1167
- const steps = keyframes?.[name];
1168
- return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
1169
- }
1170
- var RESOLVERS = {
1171
- // ── Background ─────────────────────────────────────────────────────────────
1172
- bg: ({ value, isArbitrary }, { colors }) => {
1173
- const color = resolveColor(value, colors, isArbitrary);
1174
- if (!color) return null;
1175
- return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1176
- },
1177
- "bg-opacity": ({ value, isArbitrary }, _) => {
1178
- if (!getEffectiveIsWeb()) return null;
1179
- const n = parseFloat(value);
1180
- if (isNaN(n)) return null;
1181
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1182
- return { "--bg-opacity": v };
1183
- },
1184
- // ── Text ───────────────────────────────────────────────────────────────────
1185
- text: ({ value, isArbitrary }, { colors, fontSize }) => {
1186
- const size = resolveFontSize(value, fontSize, false);
1187
- if (!isArbitrary && size !== null) return { fontSize: size };
1188
- if (isArbitrary) {
1189
- if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1190
- return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1191
- }
1192
- return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1193
- }
1194
- const color = resolveColor(value, colors, false);
1195
- if (!color) return null;
1196
- return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1197
- },
1198
- "text-opacity": ({ value, isArbitrary }, _) => {
1199
- if (!getEffectiveIsWeb()) return null;
1200
- const n = parseFloat(value);
1201
- if (isNaN(n)) return null;
1202
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1203
- return { "--text-opacity": v };
1204
- },
1205
- // ── Padding & margin ───────────────────────────────────────────────────────
1206
- // Generated below via makeSpacingResolver() — see PADDING_MARGIN_PROPS.
1207
- // ── Sizing ─────────────────────────────────────────────────────────────────
1208
- w: ({ value, isArbitrary }, { spacing }) => {
1209
- const v = resolveSizing(value, spacing, isArbitrary);
1210
- return v !== null ? { width: v } : null;
1211
- },
1212
- h: ({ value, isArbitrary }, { spacing }) => {
1213
- const v = resolveSizing(value, spacing, isArbitrary);
1214
- return v !== null ? { height: v } : null;
1215
- },
1216
- "min-w": ({ value, isArbitrary }, { spacing }) => {
1217
- const v = resolveSizing(value, spacing, isArbitrary);
1218
- return v !== null ? { minWidth: v } : null;
1219
- },
1220
- "min-h": ({ value, isArbitrary }, { spacing }) => {
1221
- const v = resolveSizing(value, spacing, isArbitrary);
1222
- return v !== null ? { minHeight: v } : null;
1223
- },
1224
- "max-w": ({ value, isArbitrary }, { spacing }) => {
1225
- const v = resolveSizing(value, spacing, isArbitrary);
1226
- return v !== null ? { maxWidth: v } : null;
1227
- },
1228
- "max-h": ({ value, isArbitrary }, { spacing }) => {
1229
- const v = resolveSizing(value, spacing, isArbitrary);
1230
- return v !== null ? { maxHeight: v } : null;
1231
- },
965
+ var BORDER_SIDE_PROPS = {
966
+ "border-t": ["borderTopWidth", "borderTopColor"],
967
+ "border-r": ["borderRightWidth", "borderRightColor"],
968
+ "border-b": ["borderBottomWidth", "borderBottomColor"],
969
+ "border-l": ["borderLeftWidth", "borderLeftColor"]
970
+ };
971
+ var borderResolvers = {
1232
972
  // ── Border width ───────────────────────────────────────────────────────────
1233
973
  border: ({ value, isArbitrary }, { colors, borderWidth, spacing }) => {
1234
974
  if (!value) return { borderWidth: borderWidth["DEFAULT"] ?? 1 };
@@ -1285,188 +1025,128 @@ var RESOLVERS = {
1285
1025
  const r = resolveRadius(value, borderRadius, isArbitrary);
1286
1026
  return r !== null ? { borderBottomRightRadius: r } : null;
1287
1027
  },
1288
- // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1289
- grid: ({ value, isArbitrary }) => {
1290
- if (!value) return null;
1291
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1292
- const n = parseInt(value, 10);
1293
- if (isNaN(n) || n < 1 || n > 12) return null;
1294
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1295
- },
1296
- // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1297
- "grid-cols": ({ value, isArbitrary }) => {
1028
+ // ── Outline extended (web-only) ───────────────────────────────────────────
1029
+ outline: ({ value, isArbitrary }, { colors }) => {
1298
1030
  if (!getEffectiveIsWeb()) return null;
1299
- if (!value) return null;
1300
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1301
- if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1302
- const n = parseInt(value, 10);
1303
- if (isNaN(n) || n < 1 || n > 12) return null;
1304
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1031
+ if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1032
+ if (value === "none") return { outline: "none", outlineOffset: "0" };
1033
+ if (isArbitrary) {
1034
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1035
+ return { outlineColor: value };
1036
+ }
1037
+ const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1038
+ if (value in widths) return { outlineWidth: widths[value] };
1039
+ const color = resolveColor(value, colors, false);
1040
+ return color ? { outlineColor: color } : null;
1305
1041
  },
1306
- "grid-rows": ({ value, isArbitrary }) => {
1042
+ "outline-offset": ({ value, isArbitrary }) => {
1307
1043
  if (!getEffectiveIsWeb()) return null;
1308
- if (!value) return null;
1309
- if (isArbitrary) return { gridTemplateRows: value };
1310
- if (value === "none") return { gridTemplateRows: "none" };
1311
- const n = parseInt(value, 10);
1312
- if (isNaN(n) || n < 1) return null;
1313
- return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1044
+ if (isArbitrary) return { outlineOffset: value };
1045
+ const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1046
+ return offsets[value] ? { outlineOffset: offsets[value] } : null;
1314
1047
  },
1315
- // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1316
- "grid-flow": ({ value }) => {
1048
+ // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1049
+ // These return special __divide* markers that resolver.ts converts to
1050
+ // .cls > * + * { border-... } CSS rules. No inline style is applied.
1051
+ "divide-x": ({ value, isArbitrary }) => {
1317
1052
  if (!getEffectiveIsWeb()) return null;
1318
- const flows = {
1319
- row: "row",
1320
- col: "column",
1321
- dense: "dense",
1322
- "row-dense": "row dense",
1323
- "col-dense": "column dense"
1324
- };
1325
- const flow = flows[value];
1326
- return flow ? { gridAutoFlow: flow } : null;
1053
+ if (value === "reverse") return null;
1054
+ if (!value) return { __divideX: 1 };
1055
+ const n = parseFloat(value);
1056
+ return isNaN(n) ? null : { __divideX: n };
1327
1057
  },
1328
- // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1329
- "auto-cols": ({ value, isArbitrary }) => {
1058
+ "divide-y": ({ value, isArbitrary }) => {
1330
1059
  if (!getEffectiveIsWeb()) return null;
1331
- if (isArbitrary) return { gridAutoColumns: value };
1332
- const presets = {
1333
- auto: "auto",
1334
- min: "min-content",
1335
- max: "max-content",
1336
- fr: "minmax(0, 1fr)"
1337
- };
1338
- const v = presets[value];
1339
- return v ? { gridAutoColumns: v } : null;
1060
+ if (value === "reverse") return null;
1061
+ if (!value) return { __divideY: 1 };
1062
+ const n = parseFloat(value);
1063
+ return isNaN(n) ? null : { __divideY: n };
1340
1064
  },
1341
- "auto-rows": ({ value, isArbitrary }) => {
1065
+ divide: ({ value, isArbitrary }, { colors }) => {
1342
1066
  if (!getEffectiveIsWeb()) return null;
1343
- if (isArbitrary) return { gridAutoRows: value };
1344
- const presets = {
1345
- auto: "auto",
1346
- min: "min-content",
1347
- max: "max-content",
1348
- fr: "minmax(0, 1fr)"
1067
+ if (isArbitrary) return { __divideColor: value };
1068
+ const styleTokens = {
1069
+ solid: "solid",
1070
+ dashed: "dashed",
1071
+ dotted: "dotted",
1072
+ double: "double",
1073
+ none: "none"
1349
1074
  };
1350
- const v = presets[value];
1351
- return v ? { gridAutoRows: v } : null;
1352
- },
1353
- // ── Grid column placement (web-only) ──────────────────────────────────────
1354
- "col-span": ({ value }) => {
1355
- if (!getEffectiveIsWeb()) return null;
1356
- if (value === "full") return { gridColumn: "1 / -1" };
1357
- const n = parseInt(value, 10);
1358
- return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1359
- },
1360
- "col-start": ({ value, isArbitrary }) => {
1361
- if (!getEffectiveIsWeb()) return null;
1362
- if (value === "auto") return { gridColumnStart: "auto" };
1363
- if (isArbitrary) return { gridColumnStart: value };
1364
- const n = parseInt(value, 10);
1365
- return isNaN(n) ? null : { gridColumnStart: n };
1366
- },
1367
- "col-end": ({ value, isArbitrary }) => {
1368
- if (!getEffectiveIsWeb()) return null;
1369
- if (value === "auto") return { gridColumnEnd: "auto" };
1370
- if (isArbitrary) return { gridColumnEnd: value };
1371
- const n = parseInt(value, 10);
1372
- return isNaN(n) ? null : { gridColumnEnd: n };
1373
- },
1374
- col: ({ value, isArbitrary }) => {
1375
- if (!getEffectiveIsWeb()) return null;
1376
- if (!value || value === "auto") return { gridColumn: "auto" };
1377
- if (isArbitrary) return { gridColumn: value };
1378
- return null;
1379
- },
1380
- // ── Grid row placement (web-only) ─────────────────────────────────────────
1381
- "row-span": ({ value }) => {
1382
- if (!getEffectiveIsWeb()) return null;
1383
- if (value === "full") return { gridRow: "1 / -1" };
1384
- const n = parseInt(value, 10);
1385
- return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1386
- },
1387
- "row-start": ({ value, isArbitrary }) => {
1388
- if (!getEffectiveIsWeb()) return null;
1389
- if (value === "auto") return { gridRowStart: "auto" };
1390
- if (isArbitrary) return { gridRowStart: value };
1391
- const n = parseInt(value, 10);
1392
- return isNaN(n) ? null : { gridRowStart: n };
1393
- },
1394
- "row-end": ({ value, isArbitrary }) => {
1395
- if (!getEffectiveIsWeb()) return null;
1396
- if (value === "auto") return { gridRowEnd: "auto" };
1397
- if (isArbitrary) return { gridRowEnd: value };
1398
- const n = parseInt(value, 10);
1399
- return isNaN(n) ? null : { gridRowEnd: n };
1075
+ if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1076
+ const color = resolveColor(value, colors, false);
1077
+ return color ? { __divideColor: color } : null;
1400
1078
  },
1401
- row: ({ value, isArbitrary }) => {
1402
- if (!getEffectiveIsWeb()) return null;
1403
- if (!value || value === "auto") return { gridRow: "auto" };
1404
- if (isArbitrary) return { gridRow: value };
1079
+ // ── Ring ───────────────────────────────────────────────────────────────────
1080
+ // Web: box-shadow outline ring (doesn't affect layout).
1081
+ // Native: React Native has no box-shadow, so this falls back to borderWidth/
1082
+ // borderColor — the closest visual approximation (used by other RN Tailwind-
1083
+ // likes for the same reason). Unlike the web ring, this DOES affect layout,
1084
+ // and it shares its properties with the `border` utility — combining
1085
+ // `border-*` and `ring-*` on the same native element means whichever class
1086
+ // comes later wins, since both ultimately set borderWidth/borderColor.
1087
+ ring: ({ value, isArbitrary }, { colors }) => {
1088
+ const onWeb = getEffectiveIsWeb();
1089
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1090
+ const DEFAULT_WIDTH = 3;
1091
+ const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
1092
+ if (!value) {
1093
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1094
+ }
1095
+ if (value === "inset") {
1096
+ return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1097
+ }
1098
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1099
+ if (!isArbitrary && value in widthTokens) {
1100
+ const w = widthTokens[value];
1101
+ return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
1102
+ }
1103
+ if (isArbitrary) {
1104
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1105
+ if (numMatch) {
1106
+ const unit = numMatch[2] ?? "px";
1107
+ if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1108
+ return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
1109
+ }
1110
+ return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
1111
+ }
1112
+ const color = resolveColor(value, colors, false);
1113
+ if (color) {
1114
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
1115
+ }
1405
1116
  return null;
1406
1117
  },
1407
- // ── Grid alignment (web-only) ──────────────────────────────────────────────
1408
- // place-items is shorthand for align-items + justify-items on a grid container
1409
- "place-items": ({ value }) => {
1410
- if (!getEffectiveIsWeb()) return null;
1411
- const map = {
1412
- start: "start",
1413
- end: "end",
1414
- center: "center",
1415
- stretch: "stretch",
1416
- baseline: "baseline"
1417
- };
1418
- return map[value] ? { placeItems: map[value] } : null;
1419
- },
1420
- // place-content is shorthand for align-content + justify-content on a grid container
1421
- "place-content": ({ value }) => {
1422
- if (!getEffectiveIsWeb()) return null;
1423
- const map = {
1424
- start: "start",
1425
- end: "end",
1426
- center: "center",
1427
- stretch: "stretch",
1428
- between: "space-between",
1429
- around: "space-around",
1430
- evenly: "space-evenly",
1431
- baseline: "baseline"
1432
- };
1433
- return map[value] ? { placeContent: map[value] } : null;
1434
- },
1435
- // justify-items controls inline-axis alignment of grid items within their cells
1436
- "justify-items": ({ value }) => {
1437
- if (!getEffectiveIsWeb()) return null;
1438
- const map = {
1439
- start: "start",
1440
- end: "end",
1441
- center: "center",
1442
- stretch: "stretch"
1443
- };
1444
- return map[value] ? { justifyItems: map[value] } : null;
1445
- },
1446
- // place-self is shorthand for align-self + justify-self on a grid item
1447
- "place-self": ({ value }) => {
1448
- if (!getEffectiveIsWeb()) return null;
1449
- const map = {
1450
- auto: "auto",
1451
- start: "start",
1452
- end: "end",
1453
- center: "center",
1454
- stretch: "stretch"
1455
- };
1456
- return map[value] ? { placeSelf: map[value] } : null;
1457
- },
1458
- // justify-self controls inline-axis self-alignment of a grid item
1459
- "justify-self": ({ value }) => {
1118
+ // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
1119
+ // create a gap between the element and the ring. There's no native
1120
+ // equivalent to approximate that with (a border can't create a gap outside
1121
+ // its own element without an extra wrapper view), so this stays a no-op
1122
+ // on native rather than rendering a misleading half-translation.
1123
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1460
1124
  if (!getEffectiveIsWeb()) return null;
1461
- const map = {
1462
- auto: "auto",
1463
- start: "start",
1464
- end: "end",
1465
- center: "center",
1466
- stretch: "stretch"
1125
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1126
+ const DEFAULT_RING_WIDTH = 3;
1127
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1128
+ let offsetWidth;
1129
+ if (!isArbitrary) {
1130
+ offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
1131
+ } else {
1132
+ offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
1133
+ }
1134
+ if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
1135
+ const isZero = offsetWidth === 0 || offsetWidth === "0";
1136
+ const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
1137
+ return {
1138
+ 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}`
1467
1139
  };
1468
- return map[value] ? { justifySelf: map[value] } : null;
1469
- },
1140
+ }
1141
+ };
1142
+ for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
1143
+ borderResolvers[utility] = makeBorderSideResolver(widthProp, colorProp);
1144
+ }
1145
+
1146
+ // src/core/resolvers/filters.ts
1147
+ 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,)";
1148
+ 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,)";
1149
+ var filterResolvers = {
1470
1150
  // ── CSS Filters (composable via CSS variables, web-only) ─────────────────
1471
1151
  blur: ({ value, isArbitrary }) => {
1472
1152
  if (!getEffectiveIsWeb()) return null;
@@ -1601,410 +1281,606 @@ var RESOLVERS = {
1601
1281
  if (value === "none") return { backdropFilter: "none" };
1602
1282
  return null;
1603
1283
  },
1604
- // ── Background gradient (CSS variable gradient stops, web-only) ──────────
1605
- "bg-gradient-to": ({ value }) => {
1606
- if (!getEffectiveIsWeb()) return null;
1607
- const directions = {
1608
- t: "to top",
1609
- tr: "to top right",
1610
- r: "to right",
1611
- br: "to bottom right",
1612
- b: "to bottom",
1613
- bl: "to bottom left",
1614
- l: "to left",
1615
- tl: "to top left"
1616
- };
1617
- const dir = directions[value];
1618
- if (!dir) return null;
1619
- return {
1620
- backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
1621
- };
1622
- },
1623
- from: ({ value, isArbitrary }, { colors }) => {
1624
- if (!getEffectiveIsWeb()) return null;
1625
- const color = resolveColor(value, colors, isArbitrary);
1626
- if (!color) return null;
1627
- return {
1628
- "--kb-gradient-from": color,
1629
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
1630
- };
1631
- },
1632
- via: ({ value, isArbitrary }, { colors }) => {
1633
- if (!getEffectiveIsWeb()) return null;
1634
- const color = resolveColor(value, colors, isArbitrary);
1635
- if (!color) return null;
1636
- return {
1637
- "--kb-gradient-via": color,
1638
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
1639
- };
1640
- },
1641
- to: ({ value, isArbitrary }, { colors }) => {
1642
- if (!getEffectiveIsWeb()) return null;
1643
- const color = resolveColor(value, colors, isArbitrary);
1644
- if (!color) return null;
1645
- return { "--kb-gradient-to": color };
1646
- },
1647
- // ── Transform extras (web-only) ───────────────────────────────────────────
1648
- "skew-x": ({ value, negative, isArbitrary }) => {
1649
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1650
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1651
- return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
1652
- },
1653
- "skew-y": ({ value, negative, isArbitrary }) => {
1654
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1655
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1656
- return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
1657
- },
1658
- // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
1659
- transform: ({ value, isArbitrary }) => {
1660
- if (!getEffectiveIsWeb()) return null;
1661
- if (isArbitrary) return { transform: value.replace(/_/g, " ") };
1662
- if (value === "none") return { transform: "none" };
1663
- return null;
1664
- },
1665
- // Transform origin
1666
- origin: ({ value, isArbitrary }) => {
1284
+ // ── Mix / background blend mode (web-only) ───────────────────────────────
1285
+ "mix-blend": ({ value }) => {
1667
1286
  if (!getEffectiveIsWeb()) return null;
1668
- if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
1669
- const origins = {
1670
- center: "center",
1671
- top: "top",
1672
- "top-right": "top right",
1673
- right: "right",
1674
- "bottom-right": "bottom right",
1675
- bottom: "bottom",
1676
- "bottom-left": "bottom left",
1677
- left: "left",
1678
- "top-left": "top left"
1679
- };
1680
- return origins[value] ? { transformOrigin: origins[value] } : null;
1287
+ const modes = [
1288
+ "normal",
1289
+ "multiply",
1290
+ "screen",
1291
+ "overlay",
1292
+ "darken",
1293
+ "lighten",
1294
+ "color-dodge",
1295
+ "color-burn",
1296
+ "hard-light",
1297
+ "soft-light",
1298
+ "difference",
1299
+ "exclusion",
1300
+ "hue",
1301
+ "saturation",
1302
+ "color",
1303
+ "luminosity",
1304
+ "plus-lighter"
1305
+ ];
1306
+ return modes.includes(value) ? { mixBlendMode: value } : null;
1681
1307
  },
1682
- // ── Text decoration advanced (web-only) ───────────────────────────────────
1683
- decoration: ({ value, isArbitrary }, { colors }) => {
1308
+ "bg-blend": ({ value }) => {
1684
1309
  if (!getEffectiveIsWeb()) return null;
1685
- if (isArbitrary) {
1686
- if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
1687
- return { textDecorationColor: value };
1688
- }
1689
- const thickTokens = {
1690
- auto: "auto",
1691
- "from-font": "from-font",
1692
- "0": "0px",
1693
- "1": "1px",
1694
- "2": "2px",
1695
- "4": "4px",
1696
- "8": "8px"
1697
- };
1698
- if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
1699
- const styleTokens = {
1700
- solid: "solid",
1701
- dashed: "dashed",
1702
- dotted: "dotted",
1703
- double: "double",
1704
- wavy: "wavy"
1705
- };
1706
- if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
1707
- const color = resolveColor(value, colors, false);
1708
- return color ? { textDecorationColor: color } : null;
1310
+ const modes = [
1311
+ "normal",
1312
+ "multiply",
1313
+ "screen",
1314
+ "overlay",
1315
+ "darken",
1316
+ "lighten",
1317
+ "color-dodge",
1318
+ "color-burn",
1319
+ "hard-light",
1320
+ "soft-light",
1321
+ "difference",
1322
+ "exclusion",
1323
+ "hue",
1324
+ "saturation",
1325
+ "color",
1326
+ "luminosity"
1327
+ ];
1328
+ return modes.includes(value) ? { backgroundBlendMode: value } : null;
1709
1329
  },
1710
- "underline-offset": ({ value, isArbitrary }) => {
1330
+ // ── Will-change (web-only) ─────────────────────────────────────────────────
1331
+ "will-change": ({ value, isArbitrary }) => {
1711
1332
  if (!getEffectiveIsWeb()) return null;
1712
- if (isArbitrary) return { textUnderlineOffset: value };
1713
- const offsets = {
1333
+ if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1334
+ const presets = {
1714
1335
  auto: "auto",
1715
- "0": "0px",
1716
- "1": "1px",
1717
- "2": "2px",
1718
- "4": "4px",
1719
- "8": "8px"
1336
+ scroll: "scroll-position",
1337
+ contents: "contents",
1338
+ transform: "transform"
1720
1339
  };
1721
- return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
1722
- },
1723
- // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
1724
- // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
1725
- // the literal string 'you can see it' quotes included), so it passes straight through as a
1726
- // valid `content: '*'` CSS value with no extra wrapping needed.
1727
- content: ({ value, isArbitrary }) => {
1728
- if (!getEffectiveIsWeb()) return null;
1729
- if (isArbitrary) return { content: value };
1730
- const presets = { none: "none" };
1731
- return value in presets ? { content: presets[value] } : null;
1732
- },
1733
- // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
1734
- // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
1735
- // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
1736
- // presumably a typo) parses, which is why this only fires when the arbitrary value
1737
- // itself contains a "property:value" pair; anything else (no colon) still resolves
1738
- // to null exactly as before this resolver existed, rather than guessing a property.
1739
- //
1740
- // Only the FIRST colon splits property from value — the value itself may contain
1741
- // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
1742
- // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
1743
- // custom property) key, so it can be used as the style object key as-written with no
1744
- // extra case conversion — it only needs to look like a plausible CSS property name.
1745
- "": ({ value, isArbitrary, negative }) => {
1746
- if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
1747
- if (negative) return null;
1748
- const colonIdx = value.indexOf(":");
1749
- if (colonIdx <= 0) return null;
1750
- const prop = value.slice(0, colonIdx).trim();
1751
- const cssValue = value.slice(colonIdx + 1).trim();
1752
- if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
1753
- return { [prop]: cssValue };
1754
- },
1755
- // ── Outline extended (web-only) ───────────────────────────────────────────
1756
- outline: ({ value, isArbitrary }, { colors }) => {
1757
- if (!getEffectiveIsWeb()) return null;
1758
- if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1759
- if (value === "none") return { outline: "none", outlineOffset: "0" };
1760
- if (isArbitrary) {
1761
- if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1762
- return { outlineColor: value };
1763
- }
1764
- const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1765
- if (value in widths) return { outlineWidth: widths[value] };
1766
- const color = resolveColor(value, colors, false);
1767
- return color ? { outlineColor: color } : null;
1768
- },
1769
- "outline-offset": ({ value, isArbitrary }) => {
1770
- if (!getEffectiveIsWeb()) return null;
1771
- if (isArbitrary) return { outlineOffset: value };
1772
- const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1773
- return offsets[value] ? { outlineOffset: offsets[value] } : null;
1340
+ return presets[value] ? { willChange: presets[value] } : null;
1341
+ }
1342
+ };
1343
+
1344
+ // src/core/resolvers/layout.ts
1345
+ var _standalone = null;
1346
+ function buildStandalone() {
1347
+ const web = getEffectiveIsWeb();
1348
+ return {
1349
+ // Display
1350
+ // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
1351
+ // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
1352
+ // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
1353
+ // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
1354
+ // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
1355
+ // time, but without it these utilities couldn't re-show a `hidden` element on
1356
+ // native either (null → no style applied → still display:'none').
1357
+ flex: { display: "flex" },
1358
+ block: web ? { display: "block" } : null,
1359
+ "inline-block": { display: web ? "inline-block" : "flex" },
1360
+ inline: { display: web ? "inline" : "flex" },
1361
+ grid: web ? { display: "grid" } : null,
1362
+ grd: web ? { display: "grid" } : null,
1363
+ "inline-flex": { display: web ? "inline-flex" : "flex" },
1364
+ "inline-grid": { display: web ? "inline-grid" : "flex" },
1365
+ hidden: { display: "none" },
1366
+ contents: web ? { display: "contents" } : null,
1367
+ "flow-root": web ? { display: "flow-root" } : null,
1368
+ // Flex direction (both web and native)
1369
+ "flex-row": { flexDirection: "row" },
1370
+ "flex-col": { flexDirection: "column" },
1371
+ "flex-row-reverse": { flexDirection: "row-reverse" },
1372
+ "flex-col-reverse": { flexDirection: "column-reverse" },
1373
+ // Flex wrap (both)
1374
+ "flex-wrap": { flexWrap: "wrap" },
1375
+ "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
1376
+ "flex-nowrap": { flexWrap: "nowrap" },
1377
+ // Flex grow / shrink (both)
1378
+ "flex-grow": { flexGrow: 1 },
1379
+ "flex-grow-0": { flexGrow: 0 },
1380
+ "flex-shrink": { flexShrink: 1 },
1381
+ "flex-shrink-0": { flexShrink: 0 },
1382
+ // Align items (both)
1383
+ "items-start": { alignItems: "flex-start" },
1384
+ "items-end": { alignItems: "flex-end" },
1385
+ "items-center": { alignItems: "center" },
1386
+ "items-baseline": { alignItems: "baseline" },
1387
+ "items-stretch": { alignItems: "stretch" },
1388
+ // Justify content (both)
1389
+ "justify-start": { justifyContent: "flex-start" },
1390
+ "justify-end": { justifyContent: "flex-end" },
1391
+ "justify-center": { justifyContent: "center" },
1392
+ "justify-between": { justifyContent: "space-between" },
1393
+ "justify-around": { justifyContent: "space-around" },
1394
+ "justify-evenly": { justifyContent: "space-evenly" },
1395
+ // Align content (both)
1396
+ "content-start": { alignContent: "flex-start" },
1397
+ "content-end": { alignContent: "flex-end" },
1398
+ "content-center": { alignContent: "center" },
1399
+ "content-between": { alignContent: "space-between" },
1400
+ "content-around": { alignContent: "space-around" },
1401
+ "content-evenly": { alignContent: "space-evenly" },
1402
+ "content-stretch": { alignContent: "stretch" },
1403
+ // Align self (both)
1404
+ "self-auto": { alignSelf: "auto" },
1405
+ "self-start": { alignSelf: "flex-start" },
1406
+ "self-end": { alignSelf: "flex-end" },
1407
+ "self-center": { alignSelf: "center" },
1408
+ "self-stretch": { alignSelf: "stretch" },
1409
+ "self-baseline": { alignSelf: "baseline" },
1410
+ // Text align (both)
1411
+ "text-left": { textAlign: "left" },
1412
+ "text-right": { textAlign: "right" },
1413
+ "text-center": { textAlign: "center" },
1414
+ "text-justify": { textAlign: "justify" },
1415
+ // Font weight shortcuts (both)
1416
+ "font-thin": { fontWeight: "100" },
1417
+ "font-extralight": { fontWeight: "200" },
1418
+ "font-light": { fontWeight: "300" },
1419
+ "font-normal": { fontWeight: "400" },
1420
+ "font-medium": { fontWeight: "500" },
1421
+ "font-semibold": { fontWeight: "600" },
1422
+ "font-bold": { fontWeight: "700" },
1423
+ "font-extrabold": { fontWeight: "800" },
1424
+ "font-black": { fontWeight: "900" },
1425
+ // Text decoration (overline is web-only)
1426
+ underline: { textDecorationLine: "underline" },
1427
+ overline: web ? { textDecorationLine: "overline" } : null,
1428
+ "line-through": { textDecorationLine: "line-through" },
1429
+ "no-underline": { textDecorationLine: "none" },
1430
+ // Text transform (both)
1431
+ uppercase: { textTransform: "uppercase" },
1432
+ lowercase: { textTransform: "lowercase" },
1433
+ capitalize: { textTransform: "capitalize" },
1434
+ "normal-case": { textTransform: "none" },
1435
+ // Font style (both)
1436
+ italic: { fontStyle: "italic" },
1437
+ "non-italic": { fontStyle: "normal" },
1438
+ // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
1439
+ relative: { position: "relative" },
1440
+ absolute: { position: "absolute" },
1441
+ fixed: web ? { position: "fixed" } : null,
1442
+ sticky: web ? { position: "sticky" } : null,
1443
+ static: web ? { position: "static" } : null,
1444
+ // Visibility (no `visibility` prop on native; use opacity instead)
1445
+ visible: web ? { visibility: "visible" } : null,
1446
+ invisible: web ? { visibility: "hidden" } : { opacity: 0 },
1447
+ // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
1448
+ "overflow-hidden": { overflow: "hidden" },
1449
+ "overflow-visible": { overflow: "visible" },
1450
+ "overflow-scroll": { overflow: "scroll" },
1451
+ "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
1452
+ // overflowX / overflowY are web-only
1453
+ "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
1454
+ "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
1455
+ "overflow-x-auto": web ? { overflowX: "auto" } : null,
1456
+ "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
1457
+ "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
1458
+ "overflow-y-auto": web ? { overflowY: "auto" } : null,
1459
+ // Object fit (web-only; use resizeMode prop on RN Image)
1460
+ "object-contain": web ? { objectFit: "contain" } : null,
1461
+ "object-cover": web ? { objectFit: "cover" } : null,
1462
+ "object-fill": web ? { objectFit: "fill" } : null,
1463
+ "object-none": web ? { objectFit: "none" } : null,
1464
+ "object-scale-down": web ? { objectFit: "scale-down" } : null,
1465
+ // Border style (both)
1466
+ "border-solid": { borderStyle: "solid" },
1467
+ "border-dashed": { borderStyle: "dashed" },
1468
+ "border-dotted": { borderStyle: "dotted" },
1469
+ "border-none": { borderWidth: 0 },
1470
+ // Border sides default width (both)
1471
+ "border-t": { borderTopWidth: 1 },
1472
+ "border-r": { borderRightWidth: 1 },
1473
+ "border-b": { borderBottomWidth: 1 },
1474
+ "border-l": { borderLeftWidth: 1 },
1475
+ // Cursor (web-only)
1476
+ "cursor-auto": web ? { cursor: "auto" } : null,
1477
+ "cursor-default": web ? { cursor: "default" } : null,
1478
+ "cursor-pointer": web ? { cursor: "pointer" } : null,
1479
+ "cursor-wait": web ? { cursor: "wait" } : null,
1480
+ "cursor-text": web ? { cursor: "text" } : null,
1481
+ "cursor-move": web ? { cursor: "move" } : null,
1482
+ "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
1483
+ // User select (web-only)
1484
+ "select-none": web ? { userSelect: "none" } : null,
1485
+ "select-text": web ? { userSelect: "text" } : null,
1486
+ "select-all": web ? { userSelect: "all" } : null,
1487
+ "select-auto": web ? { userSelect: "auto" } : null,
1488
+ // Pointer events (both)
1489
+ "pointer-events-none": { pointerEvents: "none" },
1490
+ "pointer-events-auto": { pointerEvents: "auto" },
1491
+ // Whitespace (web-only; RN Text uses numberOfLines prop instead)
1492
+ "whitespace-normal": web ? { whiteSpace: "normal" } : null,
1493
+ "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
1494
+ "whitespace-pre": web ? { whiteSpace: "pre" } : null,
1495
+ "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
1496
+ "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
1497
+ // Word break (web-only)
1498
+ "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
1499
+ "break-words": web ? { overflowWrap: "break-word" } : null,
1500
+ "break-all": web ? { wordBreak: "break-all" } : null,
1501
+ // Misc web-only
1502
+ truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
1503
+ "box-border": web ? { boxSizing: "border-box" } : null,
1504
+ "box-content": web ? { boxSizing: "content-box" } : null,
1505
+ "appearance-none": web ? { appearance: "none" } : null,
1506
+ "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
1507
+ outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
1508
+ resize: web ? { resize: "both" } : null,
1509
+ "resize-none": web ? { resize: "none" } : null,
1510
+ "resize-y": web ? { resize: "vertical" } : null,
1511
+ "resize-x": web ? { resize: "horizontal" } : null,
1512
+ antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
1513
+ "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
1514
+ "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
1515
+ "sr-only": web ? {
1516
+ position: "absolute",
1517
+ width: 1,
1518
+ height: 1,
1519
+ padding: 0,
1520
+ margin: -1,
1521
+ overflow: "hidden",
1522
+ clip: "rect(0,0,0,0)",
1523
+ whiteSpace: "nowrap",
1524
+ border: 0
1525
+ } : null,
1526
+ "not-sr-only": web ? {
1527
+ position: "static",
1528
+ width: "auto",
1529
+ height: "auto",
1530
+ padding: 0,
1531
+ margin: 0,
1532
+ overflow: "visible",
1533
+ clip: "auto",
1534
+ whiteSpace: "normal"
1535
+ } : null,
1536
+ // Lists (web-only)
1537
+ "list-none": web ? { listStyleType: "none" } : null,
1538
+ "list-disc": web ? { listStyleType: "disc" } : null,
1539
+ "list-decimal": web ? { listStyleType: "decimal" } : null,
1540
+ // Background clip (web-only)
1541
+ "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
1542
+ "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
1543
+ "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
1544
+ "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
1545
+ // Background image
1546
+ "bg-none": web ? { backgroundImage: "none" } : null,
1547
+ // Background size (web-only)
1548
+ "bg-auto": web ? { backgroundSize: "auto" } : null,
1549
+ "bg-cover": web ? { backgroundSize: "cover" } : null,
1550
+ "bg-contain": web ? { backgroundSize: "contain" } : null,
1551
+ // Background position (web-only)
1552
+ "bg-center": web ? { backgroundPosition: "center" } : null,
1553
+ "bg-top": web ? { backgroundPosition: "top" } : null,
1554
+ "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
1555
+ "bg-left": web ? { backgroundPosition: "left" } : null,
1556
+ "bg-right": web ? { backgroundPosition: "right" } : null,
1557
+ "bg-left-top": web ? { backgroundPosition: "left top" } : null,
1558
+ "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
1559
+ "bg-right-top": web ? { backgroundPosition: "right top" } : null,
1560
+ "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
1561
+ // Background repeat (web-only)
1562
+ "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
1563
+ "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
1564
+ "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
1565
+ "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
1566
+ "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
1567
+ "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
1568
+ // Background attachment (web-only)
1569
+ "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
1570
+ "bg-local": web ? { backgroundAttachment: "local" } : null,
1571
+ "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
1572
+ // Object position (web-only)
1573
+ "object-center": web ? { objectPosition: "center" } : null,
1574
+ "object-top": web ? { objectPosition: "top" } : null,
1575
+ "object-bottom": web ? { objectPosition: "bottom" } : null,
1576
+ "object-left": web ? { objectPosition: "left" } : null,
1577
+ "object-right": web ? { objectPosition: "right" } : null,
1578
+ "object-left-top": web ? { objectPosition: "left top" } : null,
1579
+ "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1580
+ "object-right-top": web ? { objectPosition: "right top" } : null,
1581
+ "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1582
+ // Table (web-only)
1583
+ table: web ? { display: "table" } : null,
1584
+ "table-auto": web ? { tableLayout: "auto" } : null,
1585
+ "table-fixed": web ? { tableLayout: "fixed" } : null,
1586
+ "caption-top": web ? { captionSide: "top" } : null,
1587
+ "caption-bottom": web ? { captionSide: "bottom" } : null,
1588
+ "border-collapse": web ? { borderCollapse: "collapse" } : null,
1589
+ "border-separate": web ? { borderCollapse: "separate" } : null,
1590
+ // List style position (web-only)
1591
+ "list-inside": web ? { listStylePosition: "inside" } : null,
1592
+ "list-outside": web ? { listStylePosition: "outside" } : null,
1593
+ // Font variant numeric (web-only)
1594
+ "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1595
+ ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1596
+ "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1597
+ "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1598
+ "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1599
+ "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1600
+ "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1601
+ "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1602
+ "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1603
+ // Isolation (web-only)
1604
+ isolate: web ? { isolation: "isolate" } : null,
1605
+ "isolation-auto": web ? { isolation: "auto" } : null,
1606
+ // Backface visibility (both iOS and Android)
1607
+ "backface-visible": { backfaceVisibility: "visible" },
1608
+ "backface-hidden": { backfaceVisibility: "hidden" },
1609
+ // CSS filters — standalone = default/full effect (web-only)
1610
+ grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1611
+ invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1612
+ sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1613
+ // Divide none
1614
+ "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1615
+ // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1616
+ group: web ? {} : null,
1617
+ peer: web ? {} : null,
1618
+ // Text-wrap (web-only)
1619
+ "text-wrap": web ? { textWrap: "wrap" } : null,
1620
+ "text-nowrap": web ? { textWrap: "nowrap" } : null,
1621
+ "text-balance": web ? { textWrap: "balance" } : null,
1622
+ "text-pretty": web ? { textWrap: "pretty" } : null,
1623
+ // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1624
+ // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1625
+ // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1626
+ // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1627
+ // is unchanged since there's no dynamic chrome to account for.
1628
+ // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1629
+ "w-screen": web ? { width: "100dvw" } : null,
1630
+ "min-h-screen": { minHeight: "100dvh" },
1631
+ "max-h-screen": { maxHeight: "100dvh" },
1632
+ "min-w-screen": web ? { minWidth: "100dvw" } : null,
1633
+ "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1634
+ // max-w named container sizes (mirrors Tailwind's container scale)
1635
+ "max-w-none": { maxWidth: "none" },
1636
+ "max-w-xs": { maxWidth: 320 },
1637
+ "max-w-sm": { maxWidth: 384 },
1638
+ "max-w-md": { maxWidth: 448 },
1639
+ "max-w-lg": { maxWidth: 512 },
1640
+ "max-w-xl": { maxWidth: 576 },
1641
+ "max-w-2xl": { maxWidth: 672 },
1642
+ "max-w-3xl": { maxWidth: 768 },
1643
+ "max-w-4xl": { maxWidth: 896 },
1644
+ "max-w-5xl": { maxWidth: 1024 },
1645
+ "max-w-6xl": { maxWidth: 1152 },
1646
+ "max-w-7xl": { maxWidth: 1280 },
1647
+ "max-w-prose": web ? { maxWidth: "65ch" } : null,
1648
+ // Extended cursors (web-only)
1649
+ "cursor-grab": web ? { cursor: "grab" } : null,
1650
+ "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1651
+ "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1652
+ "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1653
+ "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1654
+ "cursor-help": web ? { cursor: "help" } : null,
1655
+ "cursor-none": web ? { cursor: "none" } : null,
1656
+ // Overflow clip (web-only)
1657
+ "overflow-clip": web ? { overflow: "clip" } : null,
1658
+ "overflow-x-clip": web ? { overflowX: "clip" } : null,
1659
+ "overflow-y-clip": web ? { overflowY: "clip" } : null,
1660
+ // Scroll behavior (web-only)
1661
+ "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1662
+ "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1663
+ // Float (web-only)
1664
+ "float-left": web ? { float: "left" } : null,
1665
+ "float-right": web ? { float: "right" } : null,
1666
+ "float-start": web ? { float: "inline-start" } : null,
1667
+ "float-end": web ? { float: "inline-end" } : null,
1668
+ "float-none": web ? { float: "none" } : null,
1669
+ // Clear (web-only)
1670
+ "clear-left": web ? { clear: "left" } : null,
1671
+ "clear-right": web ? { clear: "right" } : null,
1672
+ "clear-both": web ? { clear: "both" } : null,
1673
+ "clear-start": web ? { clear: "inline-start" } : null,
1674
+ "clear-end": web ? { clear: "inline-end" } : null,
1675
+ "clear-none": web ? { clear: "none" } : null,
1676
+ // Vertical align (web-only)
1677
+ "align-baseline": web ? { verticalAlign: "baseline" } : null,
1678
+ "align-top": web ? { verticalAlign: "top" } : null,
1679
+ "align-middle": web ? { verticalAlign: "middle" } : null,
1680
+ "align-bottom": web ? { verticalAlign: "bottom" } : null,
1681
+ "align-text-top": web ? { verticalAlign: "text-top" } : null,
1682
+ "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1683
+ "align-sub": web ? { verticalAlign: "sub" } : null,
1684
+ "align-super": web ? { verticalAlign: "super" } : null,
1685
+ // Touch action (web-only)
1686
+ "touch-auto": web ? { touchAction: "auto" } : null,
1687
+ "touch-none": web ? { touchAction: "none" } : null,
1688
+ "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1689
+ "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1690
+ "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1691
+ "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1692
+ "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1693
+ "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1694
+ "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1695
+ "touch-manipulation": web ? { touchAction: "manipulation" } : null
1696
+ };
1697
+ }
1698
+ function getStandalone() {
1699
+ if (!_standalone) _standalone = buildStandalone();
1700
+ return _standalone;
1701
+ }
1702
+ var layoutResolvers = {
1703
+ // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1704
+ grid: ({ value, isArbitrary }) => {
1705
+ if (!value) return null;
1706
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1707
+ const n = parseInt(value, 10);
1708
+ if (isNaN(n) || n < 1 || n > 12) return null;
1709
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1774
1710
  },
1775
- // ── Columns (web-only) ─────────────────────────────────────────────────────
1776
- columns: ({ value, isArbitrary }) => {
1711
+ // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1712
+ "grid-cols": ({ value, isArbitrary }) => {
1777
1713
  if (!getEffectiveIsWeb()) return null;
1778
- if (isArbitrary) return { columnCount: value };
1779
- if (value === "auto") return { columnCount: "auto" };
1714
+ if (!value) return null;
1715
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1716
+ if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1780
1717
  const n = parseInt(value, 10);
1781
- if (!isNaN(n)) return { columnCount: n };
1782
- const widths = {
1783
- "3xs": "16rem",
1784
- "2xs": "18rem",
1785
- xs: "20rem",
1786
- sm: "24rem",
1787
- md: "28rem",
1788
- lg: "32rem",
1789
- xl: "36rem",
1790
- "2xl": "42rem",
1791
- "3xl": "48rem",
1792
- "4xl": "56rem",
1793
- "5xl": "64rem",
1794
- "6xl": "72rem",
1795
- "7xl": "80rem"
1796
- };
1797
- return widths[value] ? { columnWidth: widths[value] } : null;
1718
+ if (isNaN(n) || n < 1 || n > 12) return null;
1719
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1798
1720
  },
1799
- // ── Mix / background blend mode (web-only) ───────────────────────────────
1800
- "mix-blend": ({ value }) => {
1721
+ "grid-rows": ({ value, isArbitrary }) => {
1801
1722
  if (!getEffectiveIsWeb()) return null;
1802
- const modes = [
1803
- "normal",
1804
- "multiply",
1805
- "screen",
1806
- "overlay",
1807
- "darken",
1808
- "lighten",
1809
- "color-dodge",
1810
- "color-burn",
1811
- "hard-light",
1812
- "soft-light",
1813
- "difference",
1814
- "exclusion",
1815
- "hue",
1816
- "saturation",
1817
- "color",
1818
- "luminosity",
1819
- "plus-lighter"
1820
- ];
1821
- return modes.includes(value) ? { mixBlendMode: value } : null;
1723
+ if (!value) return null;
1724
+ if (isArbitrary) return { gridTemplateRows: value };
1725
+ if (value === "none") return { gridTemplateRows: "none" };
1726
+ const n = parseInt(value, 10);
1727
+ if (isNaN(n) || n < 1) return null;
1728
+ return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1822
1729
  },
1823
- "bg-blend": ({ value }) => {
1730
+ // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1731
+ "grid-flow": ({ value }) => {
1824
1732
  if (!getEffectiveIsWeb()) return null;
1825
- const modes = [
1826
- "normal",
1827
- "multiply",
1828
- "screen",
1829
- "overlay",
1830
- "darken",
1831
- "lighten",
1832
- "color-dodge",
1833
- "color-burn",
1834
- "hard-light",
1835
- "soft-light",
1836
- "difference",
1837
- "exclusion",
1838
- "hue",
1839
- "saturation",
1840
- "color",
1841
- "luminosity"
1842
- ];
1843
- return modes.includes(value) ? { backgroundBlendMode: value } : null;
1733
+ const flows = {
1734
+ row: "row",
1735
+ col: "column",
1736
+ dense: "dense",
1737
+ "row-dense": "row dense",
1738
+ "col-dense": "column dense"
1739
+ };
1740
+ const flow = flows[value];
1741
+ return flow ? { gridAutoFlow: flow } : null;
1844
1742
  },
1845
- // ── Will-change (web-only) ─────────────────────────────────────────────────
1846
- "will-change": ({ value, isArbitrary }) => {
1743
+ // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1744
+ "auto-cols": ({ value, isArbitrary }) => {
1847
1745
  if (!getEffectiveIsWeb()) return null;
1848
- if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1746
+ if (isArbitrary) return { gridAutoColumns: value };
1849
1747
  const presets = {
1850
1748
  auto: "auto",
1851
- scroll: "scroll-position",
1852
- contents: "contents",
1853
- transform: "transform"
1749
+ min: "min-content",
1750
+ max: "max-content",
1751
+ fr: "minmax(0, 1fr)"
1854
1752
  };
1855
- return presets[value] ? { willChange: presets[value] } : null;
1856
- },
1857
- // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1858
- // These return special __divide* markers that resolver.ts converts to
1859
- // .cls > * + * { border-... } CSS rules. No inline style is applied.
1860
- "divide-x": ({ value, isArbitrary }) => {
1861
- if (!getEffectiveIsWeb()) return null;
1862
- if (value === "reverse") return null;
1863
- if (!value) return { __divideX: 1 };
1864
- const n = parseFloat(value);
1865
- return isNaN(n) ? null : { __divideX: n };
1866
- },
1867
- "divide-y": ({ value, isArbitrary }) => {
1868
- if (!getEffectiveIsWeb()) return null;
1869
- if (value === "reverse") return null;
1870
- if (!value) return { __divideY: 1 };
1871
- const n = parseFloat(value);
1872
- return isNaN(n) ? null : { __divideY: n };
1753
+ const v = presets[value];
1754
+ return v ? { gridAutoColumns: v } : null;
1873
1755
  },
1874
- divide: ({ value, isArbitrary }, { colors }) => {
1756
+ "auto-rows": ({ value, isArbitrary }) => {
1875
1757
  if (!getEffectiveIsWeb()) return null;
1876
- if (isArbitrary) return { __divideColor: value };
1877
- const styleTokens = {
1878
- solid: "solid",
1879
- dashed: "dashed",
1880
- dotted: "dotted",
1881
- double: "double",
1882
- none: "none"
1883
- };
1884
- if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1885
- const color = resolveColor(value, colors, false);
1886
- return color ? { __divideColor: color } : null;
1887
- },
1888
- // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
1889
- // Named presets give cross-platform shadow; arbitrary is web-only.
1890
- "text-shadow": ({ value, isArbitrary }, { colors }) => {
1891
- if (value === "none") {
1892
- return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
1893
- }
1894
- const webPresets = {
1895
- "": "0 2px 4px rgba(0,0,0,0.3)",
1896
- sm: "0 1px 2px rgba(0,0,0,0.3)",
1897
- md: "0 4px 6px rgba(0,0,0,0.3)",
1898
- lg: "0 8px 16px rgba(0,0,0,0.5)",
1899
- xl: "0 16px 32px rgba(0,0,0,0.5)"
1900
- };
1901
- const nativePresets = {
1902
- "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
1903
- sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
1904
- md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
1905
- lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
1906
- xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
1758
+ if (isArbitrary) return { gridAutoRows: value };
1759
+ const presets = {
1760
+ auto: "auto",
1761
+ min: "min-content",
1762
+ max: "max-content",
1763
+ fr: "minmax(0, 1fr)"
1907
1764
  };
1908
- if (value in webPresets) {
1909
- if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
1910
- return nativePresets[value] ?? null;
1911
- }
1912
- if (isArbitrary) {
1913
- return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
1914
- }
1915
- const color = resolveColor(value, colors, false);
1916
- if (color) {
1917
- return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
1918
- }
1919
- return null;
1765
+ const v = presets[value];
1766
+ return v ? { gridAutoRows: v } : null;
1920
1767
  },
1921
- // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
1922
- perspective: ({ value, isArbitrary }) => {
1923
- if (isArbitrary) {
1924
- const numPx = parseFloat(value);
1925
- if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
1926
- return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
1927
- }
1928
- if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
1929
- const n = parseFloat(value);
1930
- if (isNaN(n)) return null;
1931
- return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
1768
+ // ── Grid column placement (web-only) ──────────────────────────────────────
1769
+ "col-span": ({ value }) => {
1770
+ if (!getEffectiveIsWeb()) return null;
1771
+ if (value === "full") return { gridColumn: "1 / -1" };
1772
+ const n = parseInt(value, 10);
1773
+ return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1932
1774
  },
1933
- // ── Tint color (native-only, for Image and icon components) ──────────────
1934
- tint: ({ value, isArbitrary }, { colors }) => {
1935
- if (getEffectiveIsWeb()) return null;
1936
- const color = resolveColor(value, colors, isArbitrary);
1937
- return color ? { tintColor: color } : null;
1775
+ "col-start": ({ value, isArbitrary }) => {
1776
+ if (!getEffectiveIsWeb()) return null;
1777
+ if (value === "auto") return { gridColumnStart: "auto" };
1778
+ if (isArbitrary) return { gridColumnStart: value };
1779
+ const n = parseInt(value, 10);
1780
+ return isNaN(n) ? null : { gridColumnStart: n };
1938
1781
  },
1939
- // ── Size shorthand (sets width AND height in one utility) ────────────────
1940
- size: ({ value, isArbitrary }, { spacing }) => {
1941
- const v = resolveSizing(value, spacing, isArbitrary);
1942
- return v !== null ? { width: v, height: v } : null;
1782
+ "col-end": ({ value, isArbitrary }) => {
1783
+ if (!getEffectiveIsWeb()) return null;
1784
+ if (value === "auto") return { gridColumnEnd: "auto" };
1785
+ if (isArbitrary) return { gridColumnEnd: value };
1786
+ const n = parseInt(value, 10);
1787
+ return isNaN(n) ? null : { gridColumnEnd: n };
1943
1788
  },
1944
- // ── Animations (CSS keyframe animations, web-only) ────────────────────────
1945
- // Each variant injects a @keyframes rule once via the __keyframe marker.
1946
- // resolver.ts detects __keyframe, injects the rule, then strips the marker
1947
- // so it never reaches element inline styles.
1948
- animate: ({ value, isArbitrary }, theme) => {
1789
+ col: ({ value, isArbitrary }) => {
1949
1790
  if (!getEffectiveIsWeb()) return null;
1950
- if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
1951
- if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
1952
- const custom = theme.animation?.[value];
1953
- return custom ? buildAnimationValue(custom, theme.keyframes) : null;
1791
+ if (!value || value === "auto") return { gridColumn: "auto" };
1792
+ if (isArbitrary) return { gridColumn: value };
1793
+ return null;
1954
1794
  },
1955
- // ── Line-clamp (web-only) ─────────────────────────────────────────────────
1956
- "line-clamp": ({ value }) => {
1795
+ // ── Grid row placement (web-only) ─────────────────────────────────────────
1796
+ "row-span": ({ value }) => {
1957
1797
  if (!getEffectiveIsWeb()) return null;
1958
- if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
1798
+ if (value === "full") return { gridRow: "1 / -1" };
1959
1799
  const n = parseInt(value, 10);
1960
- if (isNaN(n) || n < 1) return null;
1961
- return {
1962
- overflow: "hidden",
1963
- display: "-webkit-box",
1964
- WebkitBoxOrient: "vertical",
1965
- WebkitLineClamp: n
1800
+ return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1801
+ },
1802
+ "row-start": ({ value, isArbitrary }) => {
1803
+ if (!getEffectiveIsWeb()) return null;
1804
+ if (value === "auto") return { gridRowStart: "auto" };
1805
+ if (isArbitrary) return { gridRowStart: value };
1806
+ const n = parseInt(value, 10);
1807
+ return isNaN(n) ? null : { gridRowStart: n };
1808
+ },
1809
+ "row-end": ({ value, isArbitrary }) => {
1810
+ if (!getEffectiveIsWeb()) return null;
1811
+ if (value === "auto") return { gridRowEnd: "auto" };
1812
+ if (isArbitrary) return { gridRowEnd: value };
1813
+ const n = parseInt(value, 10);
1814
+ return isNaN(n) ? null : { gridRowEnd: n };
1815
+ },
1816
+ row: ({ value, isArbitrary }) => {
1817
+ if (!getEffectiveIsWeb()) return null;
1818
+ if (!value || value === "auto") return { gridRow: "auto" };
1819
+ if (isArbitrary) return { gridRow: value };
1820
+ return null;
1821
+ },
1822
+ // ── Grid alignment (web-only) ──────────────────────────────────────────────
1823
+ // place-items is shorthand for align-items + justify-items on a grid container
1824
+ "place-items": ({ value }) => {
1825
+ if (!getEffectiveIsWeb()) return null;
1826
+ const map = {
1827
+ start: "start",
1828
+ end: "end",
1829
+ center: "center",
1830
+ stretch: "stretch",
1831
+ baseline: "baseline"
1966
1832
  };
1833
+ return map[value] ? { placeItems: map[value] } : null;
1967
1834
  },
1968
- // ── Caret color (web-only) ────────────────────────────────────────────────
1969
- caret: ({ value, isArbitrary }, { colors }) => {
1835
+ // place-content is shorthand for align-content + justify-content on a grid container
1836
+ "place-content": ({ value }) => {
1970
1837
  if (!getEffectiveIsWeb()) return null;
1971
- if (value === "auto" || value === "transparent") return { caretColor: value };
1972
- const color = resolveColor(value, colors, isArbitrary);
1973
- return color ? { caretColor: color } : null;
1838
+ const map = {
1839
+ start: "start",
1840
+ end: "end",
1841
+ center: "center",
1842
+ stretch: "stretch",
1843
+ between: "space-between",
1844
+ around: "space-around",
1845
+ evenly: "space-evenly",
1846
+ baseline: "baseline"
1847
+ };
1848
+ return map[value] ? { placeContent: map[value] } : null;
1974
1849
  },
1975
- // ── Accent color (web-only) ───────────────────────────────────────────────
1976
- accent: ({ value, isArbitrary }, { colors }) => {
1850
+ // justify-items controls inline-axis alignment of grid items within their cells
1851
+ "justify-items": ({ value }) => {
1977
1852
  if (!getEffectiveIsWeb()) return null;
1978
- if (value === "auto") return { accentColor: "auto" };
1979
- const color = resolveColor(value, colors, isArbitrary);
1980
- return color ? { accentColor: color } : null;
1853
+ const map = {
1854
+ start: "start",
1855
+ end: "end",
1856
+ center: "center",
1857
+ stretch: "stretch"
1858
+ };
1859
+ return map[value] ? { justifyItems: map[value] } : null;
1981
1860
  },
1982
- // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
1983
- // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
1984
- // take stroke/fill as component PROPS, not style entries, so there's no
1985
- // reliable way to apply a resolved color through the `style` prop there.
1986
- // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
1987
- // color-first-then-numeric-fallback disambiguation `border` uses above.
1988
- stroke: ({ value, isArbitrary }, { colors }) => {
1861
+ // place-self is shorthand for align-self + justify-self on a grid item
1862
+ "place-self": ({ value }) => {
1989
1863
  if (!getEffectiveIsWeb()) return null;
1990
- if (!value) return null;
1991
- if (value === "none") return { stroke: "none" };
1992
- if (isArbitrary) {
1993
- const w = toNativeValue(value);
1994
- return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
1995
- }
1996
- const color = resolveColor(value, colors, false);
1997
- if (color) return { stroke: color };
1998
- const n = parseFloat(value);
1999
- return isNaN(n) ? null : { strokeWidth: n };
1864
+ const map = {
1865
+ auto: "auto",
1866
+ start: "start",
1867
+ end: "end",
1868
+ center: "center",
1869
+ stretch: "stretch"
1870
+ };
1871
+ return map[value] ? { placeSelf: map[value] } : null;
2000
1872
  },
2001
- fill: ({ value, isArbitrary }, { colors }) => {
1873
+ // justify-self controls inline-axis self-alignment of a grid item
1874
+ "justify-self": ({ value }) => {
2002
1875
  if (!getEffectiveIsWeb()) return null;
2003
- if (!value) return null;
2004
- if (value === "none") return { fill: "none" };
2005
- if (isArbitrary) return { fill: value };
2006
- const color = resolveColor(value, colors, false);
2007
- return color ? { fill: color } : null;
1876
+ const map = {
1877
+ auto: "auto",
1878
+ start: "start",
1879
+ end: "end",
1880
+ center: "center",
1881
+ stretch: "stretch"
1882
+ };
1883
+ return map[value] ? { justifySelf: map[value] } : null;
2008
1884
  },
2009
1885
  // ── Flex ───────────────────────────────────────────────────────────────────
2010
1886
  flex: ({ value, isArbitrary }, { flex }) => {
@@ -2025,10 +1901,6 @@ var RESOLVERS = {
2025
1901
  const n = parseFloat(value);
2026
1902
  return isNaN(n) ? null : { flex: n };
2027
1903
  },
2028
- basis: ({ value, isArbitrary }, { spacing }) => {
2029
- const v = resolveSizing(value, spacing, isArbitrary);
2030
- return v !== null ? { flexBasis: v } : null;
2031
- },
2032
1904
  grow: ({ value }, _) => {
2033
1905
  if (!value) return { flexGrow: 1 };
2034
1906
  const n = parseFloat(value);
@@ -2043,72 +1915,132 @@ var RESOLVERS = {
2043
1915
  const n = parseInt(value, 10);
2044
1916
  return isNaN(n) ? null : { order: negative ? -n : n };
2045
1917
  },
2046
- // ── Gap ────────────────────────────────────────────────────────────────────
2047
- gap: ({ value, negative, isArbitrary }, { spacing }) => {
2048
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2049
- return v !== null ? { gap: v } : null;
2050
- },
2051
- "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
2052
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2053
- return v !== null ? { columnGap: v } : null;
2054
- },
2055
- "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
2056
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2057
- return v !== null ? { rowGap: v } : null;
2058
- },
2059
- // ── Position ───────────────────────────────────────────────────────────────
2060
- top: ({ value, negative, isArbitrary }, { spacing }) => {
2061
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2062
- return v !== null ? { top: v } : null;
2063
- },
2064
- right: ({ value, negative, isArbitrary }, { spacing }) => {
2065
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2066
- return v !== null ? { right: v } : null;
2067
- },
2068
- bottom: ({ value, negative, isArbitrary }, { spacing }) => {
2069
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2070
- return v !== null ? { bottom: v } : null;
2071
- },
2072
- left: ({ value, negative, isArbitrary }, { spacing }) => {
2073
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2074
- return v !== null ? { left: v } : null;
2075
- },
2076
- inset: ({ value, negative, isArbitrary }, { spacing }) => {
2077
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2078
- return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
2079
- },
2080
- "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
2081
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2082
- return v !== null ? { left: v, right: v } : null;
2083
- },
2084
- "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
2085
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2086
- return v !== null ? { top: v, bottom: v } : null;
2087
- },
2088
1918
  // ── Z-index ────────────────────────────────────────────────────────────────
2089
1919
  z: ({ value, isArbitrary }, { zIndex }) => {
2090
1920
  if (isArbitrary) {
2091
- const n2 = parseInt(value);
2092
- return isNaN(n2) ? null : { zIndex: n2 };
2093
- }
2094
- const v = zIndex[value];
2095
- if (v !== void 0) {
2096
- if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
2097
- return { zIndex: v };
1921
+ const n2 = parseInt(value);
1922
+ return isNaN(n2) ? null : { zIndex: n2 };
1923
+ }
1924
+ const v = zIndex[value];
1925
+ if (v !== void 0) {
1926
+ if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
1927
+ return { zIndex: v };
1928
+ }
1929
+ const n = parseInt(value);
1930
+ return isNaN(n) ? null : { zIndex: n };
1931
+ },
1932
+ // ── Aspect ratio ───────────────────────────────────────────────────────────
1933
+ aspect: ({ value, isArbitrary }) => {
1934
+ if (isArbitrary) return { aspectRatio: value };
1935
+ const presets = { auto: "auto", square: 1, video: 16 / 9 };
1936
+ if (!(value in presets)) return null;
1937
+ const v = presets[value];
1938
+ if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
1939
+ return { aspectRatio: v };
1940
+ },
1941
+ // ── Columns (web-only) ─────────────────────────────────────────────────────
1942
+ columns: ({ value, isArbitrary }) => {
1943
+ if (!getEffectiveIsWeb()) return null;
1944
+ if (isArbitrary) return { columnCount: value };
1945
+ if (value === "auto") return { columnCount: "auto" };
1946
+ const n = parseInt(value, 10);
1947
+ if (!isNaN(n)) return { columnCount: n };
1948
+ const widths = {
1949
+ "3xs": "16rem",
1950
+ "2xs": "18rem",
1951
+ xs: "20rem",
1952
+ sm: "24rem",
1953
+ md: "28rem",
1954
+ lg: "32rem",
1955
+ xl: "36rem",
1956
+ "2xl": "42rem",
1957
+ "3xl": "48rem",
1958
+ "4xl": "56rem",
1959
+ "5xl": "64rem",
1960
+ "6xl": "72rem",
1961
+ "7xl": "80rem"
1962
+ };
1963
+ return widths[value] ? { columnWidth: widths[value] } : null;
1964
+ }
1965
+ };
1966
+
1967
+ // src/core/resolvers/typography.ts
1968
+ function resolveFontSize(value, fontSizes, isArbitrary) {
1969
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
1970
+ return fontSizes[value] ?? null;
1971
+ }
1972
+ var typographyResolvers = {
1973
+ // ── Text ───────────────────────────────────────────────────────────────────
1974
+ text: ({ value, isArbitrary }, { colors, fontSize }) => {
1975
+ const size = resolveFontSize(value, fontSize, false);
1976
+ if (!isArbitrary && size !== null) return { fontSize: size };
1977
+ if (isArbitrary) {
1978
+ if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1979
+ return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1980
+ }
1981
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
2098
1982
  }
2099
- const n = parseInt(value);
2100
- return isNaN(n) ? null : { zIndex: n };
1983
+ const color = resolveColor(value, colors, false);
1984
+ if (!color) return null;
1985
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
2101
1986
  },
2102
- // ── Opacity ────────────────────────────────────────────────────────────────
2103
- opacity: ({ value, isArbitrary }, { opacity }) => {
1987
+ "text-opacity": ({ value, isArbitrary }, _) => {
1988
+ if (!getEffectiveIsWeb()) return null;
1989
+ const n = parseFloat(value);
1990
+ if (isNaN(n)) return null;
1991
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1992
+ return { "--text-opacity": v };
1993
+ },
1994
+ // ── Text decoration advanced (web-only) ───────────────────────────────────
1995
+ decoration: ({ value, isArbitrary }, { colors }) => {
1996
+ if (!getEffectiveIsWeb()) return null;
2104
1997
  if (isArbitrary) {
2105
- const v2 = parseFloat(value);
2106
- return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
1998
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
1999
+ return { textDecorationColor: value };
2107
2000
  }
2108
- const v = opacity[value];
2109
- if (v !== void 0) return { opacity: v };
2110
- const n = parseFloat(value);
2111
- return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2001
+ const thickTokens = {
2002
+ auto: "auto",
2003
+ "from-font": "from-font",
2004
+ "0": "0px",
2005
+ "1": "1px",
2006
+ "2": "2px",
2007
+ "4": "4px",
2008
+ "8": "8px"
2009
+ };
2010
+ if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
2011
+ const styleTokens = {
2012
+ solid: "solid",
2013
+ dashed: "dashed",
2014
+ dotted: "dotted",
2015
+ double: "double",
2016
+ wavy: "wavy"
2017
+ };
2018
+ if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
2019
+ const color = resolveColor(value, colors, false);
2020
+ return color ? { textDecorationColor: color } : null;
2021
+ },
2022
+ "underline-offset": ({ value, isArbitrary }) => {
2023
+ if (!getEffectiveIsWeb()) return null;
2024
+ if (isArbitrary) return { textUnderlineOffset: value };
2025
+ const offsets = {
2026
+ auto: "auto",
2027
+ "0": "0px",
2028
+ "1": "1px",
2029
+ "2": "2px",
2030
+ "4": "4px",
2031
+ "8": "8px"
2032
+ };
2033
+ return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2034
+ },
2035
+ // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
2036
+ // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
2037
+ // the literal string 'you can see it' quotes included), so it passes straight through as a
2038
+ // valid `content: '*'` CSS value with no extra wrapping needed.
2039
+ content: ({ value, isArbitrary }) => {
2040
+ if (!getEffectiveIsWeb()) return null;
2041
+ if (isArbitrary) return { content: value };
2042
+ const presets = { none: "none" };
2043
+ return value in presets ? { content: presets[value] } : null;
2112
2044
  },
2113
2045
  // ── Font ───────────────────────────────────────────────────────────────────
2114
2046
  font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
@@ -2134,10 +2066,57 @@ var RESOLVERS = {
2134
2066
  const v = letterSpacing[value];
2135
2067
  return v !== void 0 ? { letterSpacing: v } : null;
2136
2068
  },
2137
- // ── Shadow ─────────────────────────────────────────────────────────────────
2138
- shadow: ({ value }, { shadow }) => {
2139
- const key = value === "" ? "DEFAULT" : value;
2140
- return shadow[key] ?? null;
2069
+ // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2070
+ "line-clamp": ({ value }) => {
2071
+ if (!getEffectiveIsWeb()) return null;
2072
+ if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2073
+ const n = parseInt(value, 10);
2074
+ if (isNaN(n) || n < 1) return null;
2075
+ return {
2076
+ overflow: "hidden",
2077
+ display: "-webkit-box",
2078
+ WebkitBoxOrient: "vertical",
2079
+ WebkitLineClamp: n
2080
+ };
2081
+ }
2082
+ };
2083
+
2084
+ // src/core/resolvers/transform.ts
2085
+ var transformResolvers = {
2086
+ // ── Transform extras (web-only) ───────────────────────────────────────────
2087
+ "skew-x": ({ value, negative, isArbitrary }) => {
2088
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2089
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2090
+ return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2091
+ },
2092
+ "skew-y": ({ value, negative, isArbitrary }) => {
2093
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2094
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2095
+ return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
2096
+ },
2097
+ // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
2098
+ transform: ({ value, isArbitrary }) => {
2099
+ if (!getEffectiveIsWeb()) return null;
2100
+ if (isArbitrary) return { transform: value.replace(/_/g, " ") };
2101
+ if (value === "none") return { transform: "none" };
2102
+ return null;
2103
+ },
2104
+ // Transform origin
2105
+ origin: ({ value, isArbitrary }) => {
2106
+ if (!getEffectiveIsWeb()) return null;
2107
+ if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
2108
+ const origins = {
2109
+ center: "center",
2110
+ top: "top",
2111
+ "top-right": "top right",
2112
+ right: "right",
2113
+ "bottom-right": "bottom right",
2114
+ bottom: "bottom",
2115
+ "bottom-left": "bottom left",
2116
+ left: "left",
2117
+ "top-left": "top left"
2118
+ };
2119
+ return origins[value] ? { transformOrigin: origins[value] } : null;
2141
2120
  },
2142
2121
  // ── Scale ──────────────────────────────────────────────────────────────────
2143
2122
  // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
@@ -2168,35 +2147,86 @@ var RESOLVERS = {
2168
2147
  const finalDeg = negative ? -deg : deg;
2169
2148
  return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
2170
2149
  },
2171
- // ── Translate ──────────────────────────────────────────────────────────────
2172
- "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
2173
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2174
- if (v === null) return null;
2175
- if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
2176
- if (typeof v === "string") {
2177
- const n = parseFloat(v);
2178
- return isNaN(n) ? null : { transform: [{ translateX: n }] };
2150
+ // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2151
+ perspective: ({ value, isArbitrary }) => {
2152
+ if (isArbitrary) {
2153
+ const numPx = parseFloat(value);
2154
+ if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2155
+ return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2179
2156
  }
2180
- return { transform: [{ translateX: v }] };
2157
+ if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2158
+ const n = parseFloat(value);
2159
+ if (isNaN(n)) return null;
2160
+ return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2161
+ }
2162
+ };
2163
+
2164
+ // src/core/resolvers/effects.ts
2165
+ function keyframeDeclToCSS(decl) {
2166
+ return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
2167
+ }
2168
+ function buildKeyframeCSS(name, steps) {
2169
+ const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
2170
+ return `@keyframes ${name} { ${body} }`;
2171
+ }
2172
+ var BUILTIN_ANIMATIONS = {
2173
+ none: { animation: "none" },
2174
+ spin: {
2175
+ animation: "kb-spin 1s linear infinite",
2176
+ __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
2181
2177
  },
2182
- "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
2183
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2184
- if (v === null) return null;
2185
- if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
2186
- if (typeof v === "string") {
2187
- const n = parseFloat(v);
2188
- return isNaN(n) ? null : { transform: [{ translateY: n }] };
2178
+ ping: {
2179
+ animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
2180
+ __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
2181
+ },
2182
+ pulse: {
2183
+ animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
2184
+ __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
2185
+ },
2186
+ bounce: {
2187
+ animation: "kb-bounce 1s infinite",
2188
+ __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) } }"
2189
+ }
2190
+ };
2191
+ function buildAnimationValue(animation, keyframes) {
2192
+ const words = animation.trim().split(/\s+/);
2193
+ const name = words[0];
2194
+ if (!name) return { animation };
2195
+ const builtin = BUILTIN_ANIMATIONS[name];
2196
+ if (builtin?.__keyframe) {
2197
+ const kbName = builtin.animation.split(/\s+/, 1)[0];
2198
+ return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
2199
+ }
2200
+ const steps = keyframes?.[name];
2201
+ return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
2202
+ }
2203
+ var effectResolvers = {
2204
+ // ── Opacity ────────────────────────────────────────────────────────────────
2205
+ opacity: ({ value, isArbitrary }, { opacity }) => {
2206
+ if (isArbitrary) {
2207
+ const v2 = parseFloat(value);
2208
+ return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2189
2209
  }
2190
- return { transform: [{ translateY: v }] };
2210
+ const v = opacity[value];
2211
+ if (v !== void 0) return { opacity: v };
2212
+ const n = parseFloat(value);
2213
+ return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2191
2214
  },
2192
- // ── Aspect ratio ───────────────────────────────────────────────────────────
2193
- aspect: ({ value, isArbitrary }) => {
2194
- if (isArbitrary) return { aspectRatio: value };
2195
- const presets = { auto: "auto", square: 1, video: 16 / 9 };
2196
- if (!(value in presets)) return null;
2197
- const v = presets[value];
2198
- if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
2199
- return { aspectRatio: v };
2215
+ // ── Shadow ─────────────────────────────────────────────────────────────────
2216
+ shadow: ({ value }, { shadow }) => {
2217
+ const key = value === "" ? "DEFAULT" : value;
2218
+ return shadow[key] ?? null;
2219
+ },
2220
+ // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2221
+ // Each variant injects a @keyframes rule once via the __keyframe marker.
2222
+ // resolver.ts detects __keyframe, injects the rule, then strips the marker
2223
+ // so it never reaches element inline styles.
2224
+ animate: ({ value, isArbitrary }, theme) => {
2225
+ if (!getEffectiveIsWeb()) return null;
2226
+ if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2227
+ if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2228
+ const custom = theme.animation?.[value];
2229
+ return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2200
2230
  },
2201
2231
  // ── Transition (web-only; use Animated API on native) ─────────────────────
2202
2232
  transition: ({ value }) => {
@@ -2224,91 +2254,107 @@ var RESOLVERS = {
2224
2254
  if (!isArbitrary && isNaN(parseFloat(value))) return null;
2225
2255
  const ms = isArbitrary ? value : `${value}ms`;
2226
2256
  return { transitionDelay: ms };
2257
+ }
2258
+ };
2259
+
2260
+ // src/core/resolvers/misc.ts
2261
+ var miscResolvers = {
2262
+ // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
2263
+ // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
2264
+ // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
2265
+ // presumably a typo) parses, which is why this only fires when the arbitrary value
2266
+ // itself contains a "property:value" pair; anything else (no colon) still resolves
2267
+ // to null exactly as before this resolver existed, rather than guessing a property.
2268
+ //
2269
+ // Only the FIRST colon splits property from value — the value itself may contain
2270
+ // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
2271
+ // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
2272
+ // custom property) key, so it can be used as the style object key as-written with no
2273
+ // extra case conversion — it only needs to look like a plausible CSS property name.
2274
+ "": ({ value, isArbitrary, negative }) => {
2275
+ if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
2276
+ if (negative) return null;
2277
+ const colonIdx = value.indexOf(":");
2278
+ if (colonIdx <= 0) return null;
2279
+ const prop = value.slice(0, colonIdx).trim();
2280
+ const cssValue = value.slice(colonIdx + 1).trim();
2281
+ if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
2282
+ return { [prop]: cssValue };
2227
2283
  },
2228
- // ── Ring ───────────────────────────────────────────────────────────────────
2229
- // Web: box-shadow outline ring (doesn't affect layout).
2230
- // Native: React Native has no box-shadow, so this falls back to borderWidth/
2231
- // borderColor — the closest visual approximation (used by other RN Tailwind-
2232
- // likes for the same reason). Unlike the web ring, this DOES affect layout,
2233
- // and it shares its properties with the `border` utility — combining
2234
- // `border-*` and `ring-*` on the same native element means whichever class
2235
- // comes later wins, since both ultimately set borderWidth/borderColor.
2236
- ring: ({ value, isArbitrary }, { colors }) => {
2237
- const onWeb = getEffectiveIsWeb();
2238
- const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
2239
- const DEFAULT_WIDTH = 3;
2240
- const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
2241
- if (!value) {
2242
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2284
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2285
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2286
+ // take stroke/fill as component PROPS, not style entries, so there's no
2287
+ // reliable way to apply a resolved color through the `style` prop there.
2288
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2289
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2290
+ stroke: ({ value, isArbitrary }, { colors }) => {
2291
+ if (!getEffectiveIsWeb()) return null;
2292
+ if (!value) return null;
2293
+ if (value === "none") return { stroke: "none" };
2294
+ if (isArbitrary) {
2295
+ const w = toNativeValue(value);
2296
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2243
2297
  }
2244
- if (value === "inset") {
2245
- return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2298
+ const color = resolveColor(value, colors, false);
2299
+ if (color) return { stroke: color };
2300
+ const n = parseFloat(value);
2301
+ return isNaN(n) ? null : { strokeWidth: n };
2302
+ },
2303
+ fill: ({ value, isArbitrary }, { colors }) => {
2304
+ if (!getEffectiveIsWeb()) return null;
2305
+ if (!value) return null;
2306
+ if (value === "none") return { fill: "none" };
2307
+ if (isArbitrary) return { fill: value };
2308
+ const color = resolveColor(value, colors, false);
2309
+ return color ? { fill: color } : null;
2310
+ },
2311
+ // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2312
+ // Named presets give cross-platform shadow; arbitrary is web-only.
2313
+ "text-shadow": ({ value, isArbitrary }, { colors }) => {
2314
+ if (value === "none") {
2315
+ return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2246
2316
  }
2247
- const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2248
- if (!isArbitrary && value in widthTokens) {
2249
- const w = widthTokens[value];
2250
- return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
2317
+ const webPresets = {
2318
+ "": "0 2px 4px rgba(0,0,0,0.3)",
2319
+ sm: "0 1px 2px rgba(0,0,0,0.3)",
2320
+ md: "0 4px 6px rgba(0,0,0,0.3)",
2321
+ lg: "0 8px 16px rgba(0,0,0,0.5)",
2322
+ xl: "0 16px 32px rgba(0,0,0,0.5)"
2323
+ };
2324
+ const nativePresets = {
2325
+ "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2326
+ sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2327
+ md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2328
+ lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2329
+ xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2330
+ };
2331
+ if (value in webPresets) {
2332
+ if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2333
+ return nativePresets[value] ?? null;
2251
2334
  }
2252
2335
  if (isArbitrary) {
2253
- const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
2254
- if (numMatch) {
2255
- const unit = numMatch[2] ?? "px";
2256
- if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
2257
- return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
2258
- }
2259
- return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
2336
+ return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2260
2337
  }
2261
2338
  const color = resolveColor(value, colors, false);
2262
2339
  if (color) {
2263
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
2340
+ return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2264
2341
  }
2265
2342
  return null;
2266
- },
2267
- // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
2268
- // create a gap between the element and the ring. There's no native
2269
- // equivalent to approximate that with (a border can't create a gap outside
2270
- // its own element without an extra wrapper view), so this stays a no-op
2271
- // on native rather than rendering a misleading half-translation.
2272
- "ring-offset": ({ value, isArbitrary }, _theme) => {
2273
- if (!getEffectiveIsWeb()) return null;
2274
- const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
2275
- const DEFAULT_RING_WIDTH = 3;
2276
- const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2277
- let offsetWidth;
2278
- if (!isArbitrary) {
2279
- offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
2280
- } else {
2281
- offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
2282
- }
2283
- if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
2284
- const isZero = offsetWidth === 0 || offsetWidth === "0";
2285
- const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
2286
- return {
2287
- 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}`
2288
- };
2289
- },
2290
- // ── Space between ─────────────────────────────────────────────────────────
2291
- // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2292
- // On native (RN 0.71+): uses columnGap/rowGap directly.
2293
- "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2294
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2295
- if (v === null) return null;
2296
- if (getEffectiveIsWeb()) return { __spaceX: v };
2297
- return { columnGap: v };
2298
- },
2299
- "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2300
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2301
- if (v === null) return null;
2302
- if (getEffectiveIsWeb()) return { __spaceY: v };
2303
- return { rowGap: v };
2304
2343
  }
2305
2344
  };
2306
- for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
2307
- RESOLVERS[utility] = makeSpacingResolver(prop);
2308
- }
2309
- for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
2310
- RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
2311
- }
2345
+
2346
+ // src/core/utilities.ts
2347
+ var RESOLVERS = {
2348
+ ...colorResolvers,
2349
+ ...spacingResolvers,
2350
+ ...borderResolvers,
2351
+ ...layoutResolvers,
2352
+ ...typographyResolvers,
2353
+ ...filterResolvers,
2354
+ ...transformResolvers,
2355
+ ...effectResolvers,
2356
+ ...miscResolvers
2357
+ };
2312
2358
  var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2313
2359
  function resolveUtility(parsed, theme) {
2314
2360
  if (!parsed.value) {
@@ -2356,6 +2402,47 @@ function kbachWarn(message) {
2356
2402
  );
2357
2403
  }
2358
2404
 
2405
+ // src/core/cache.ts
2406
+ var LRUCache = class {
2407
+ constructor(capacity = 1e4, onEvict) {
2408
+ this.capacity = capacity;
2409
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2410
+ this.cache = /* @__PURE__ */ new Map();
2411
+ this.onEvict = onEvict;
2412
+ }
2413
+ get(key) {
2414
+ if (!this.cache.has(key)) return void 0;
2415
+ const value = this.cache.get(key);
2416
+ this.cache.delete(key);
2417
+ this.cache.set(key, value);
2418
+ return value;
2419
+ }
2420
+ set(key, value) {
2421
+ if (this.cache.has(key)) {
2422
+ this.cache.delete(key);
2423
+ } else if (this.cache.size >= this.capacity) {
2424
+ const oldestKey = this.cache.keys().next().value;
2425
+ const oldestValue = this.cache.get(oldestKey);
2426
+ this.cache.delete(oldestKey);
2427
+ this.onEvict?.(oldestKey, oldestValue);
2428
+ }
2429
+ this.cache.set(key, value);
2430
+ return this;
2431
+ }
2432
+ has(key) {
2433
+ return this.cache.has(key);
2434
+ }
2435
+ delete(key) {
2436
+ return this.cache.delete(key);
2437
+ }
2438
+ clear() {
2439
+ this.cache.clear();
2440
+ }
2441
+ get size() {
2442
+ return this.cache.size;
2443
+ }
2444
+ };
2445
+
2359
2446
  // src/core/registry.ts
2360
2447
  var BUILTIN_MODIFIERS = {
2361
2448
  // ── Mode ─────────────────────────────────────────────────────────────────
@@ -2442,7 +2529,7 @@ function clearPluginModifiers() {
2442
2529
  _invalidate();
2443
2530
  }
2444
2531
  var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
2445
- var _namedModifierCache = /* @__PURE__ */ new Map();
2532
+ var _namedModifierCache = new LRUCache(1e4);
2446
2533
  function getNamedGroupPeerModifier(name) {
2447
2534
  const cached = _namedModifierCache.get(name);
2448
2535
  if (cached) return cached;
@@ -2613,51 +2700,10 @@ function findOuterColon(s) {
2613
2700
  return -1;
2614
2701
  }
2615
2702
 
2616
- // src/core/cache.ts
2617
- var LRUCache = class {
2618
- constructor(capacity = 1e4, onEvict) {
2619
- this.capacity = capacity;
2620
- if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2621
- this.cache = /* @__PURE__ */ new Map();
2622
- this.onEvict = onEvict;
2623
- }
2624
- get(key) {
2625
- if (!this.cache.has(key)) return void 0;
2626
- const value = this.cache.get(key);
2627
- this.cache.delete(key);
2628
- this.cache.set(key, value);
2629
- return value;
2630
- }
2631
- set(key, value) {
2632
- if (this.cache.has(key)) {
2633
- this.cache.delete(key);
2634
- } else if (this.cache.size >= this.capacity) {
2635
- const oldestKey = this.cache.keys().next().value;
2636
- const oldestValue = this.cache.get(oldestKey);
2637
- this.cache.delete(oldestKey);
2638
- this.onEvict?.(oldestKey, oldestValue);
2639
- }
2640
- this.cache.set(key, value);
2641
- return this;
2642
- }
2643
- has(key) {
2644
- return this.cache.has(key);
2645
- }
2646
- delete(key) {
2647
- return this.cache.delete(key);
2648
- }
2649
- clear() {
2650
- this.cache.clear();
2651
- }
2652
- get size() {
2653
- return this.cache.size;
2654
- }
2655
- };
2656
-
2657
2703
  // src/core/resolver.ts
2658
- var FONT_KEY = "__kbach_default_font__";
2704
+ var _defaultFontFamily;
2659
2705
  function setDefaultFontFamily(font) {
2660
- globalThis[FONT_KEY] = font;
2706
+ _defaultFontFamily = font;
2661
2707
  }
2662
2708
  var _styleEl = null;
2663
2709
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
@@ -2677,9 +2723,9 @@ function evictInjectedRule(rule) {
2677
2723
  }
2678
2724
  }
2679
2725
  var _injectedRules = new LRUCache(5e4, evictInjectedRule);
2680
- var _CSS_DISABLED_KEY = "__kbach_runtime_css_disabled__";
2726
+ var _runtimeCSSDisabled = false;
2681
2727
  function isRuntimeCSSDisabled() {
2682
- return !!globalThis[_CSS_DISABLED_KEY];
2728
+ return _runtimeCSSDisabled;
2683
2729
  }
2684
2730
  function getStyleEl() {
2685
2731
  if (_styleEl) return _styleEl;
@@ -2902,14 +2948,7 @@ function deepMerge(base, override) {
2902
2948
  }
2903
2949
  return result;
2904
2950
  }
2905
- var CONFIG_KEY = "__kbach_config_store__";
2906
- function getConfigStore() {
2907
- const g = globalThis;
2908
- if (!g[CONFIG_KEY]) {
2909
- g[CONFIG_KEY] = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
2910
- }
2911
- return g[CONFIG_KEY];
2912
- }
2951
+ var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
2913
2952
  function resolveOneRef(ref, colors) {
2914
2953
  const lastDash = ref.lastIndexOf("-");
2915
2954
  if (lastDash <= 0) return null;
@@ -2983,8 +3022,7 @@ function buildConfig(userConfig) {
2983
3022
  clearPluginUtilities();
2984
3023
  clearPluginModifiers();
2985
3024
  }
2986
- const _store = getConfigStore();
2987
- for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
3025
+ for (const k of Object.keys(configStore.customVariants)) delete configStore.customVariants[k];
2988
3026
  const pluginAPI = makePluginAPI(resolved.theme);
2989
3027
  for (const plugin of resolved.plugins) {
2990
3028
  plugin(pluginAPI);
@@ -3031,26 +3069,7 @@ function makePluginAPI(theme) {
3031
3069
  }
3032
3070
  };
3033
3071
  }
3034
- var customVariants = new Proxy(
3035
- {},
3036
- {
3037
- get(_t, p) {
3038
- return typeof p === "string" ? getConfigStore().customVariants[p] : void 0;
3039
- },
3040
- set(_t, p, v) {
3041
- if (typeof p === "string") getConfigStore().customVariants[p] = v;
3042
- return true;
3043
- },
3044
- ownKeys() {
3045
- return Object.keys(getConfigStore().customVariants);
3046
- },
3047
- getOwnPropertyDescriptor(_t, p) {
3048
- if (typeof p !== "string") return void 0;
3049
- const v = getConfigStore().customVariants[p];
3050
- return v !== void 0 ? { value: v, writable: true, enumerable: true, configurable: true } : void 0;
3051
- }
3052
- }
3053
- );
3072
+ var customVariants = configStore.customVariants;
3054
3073
 
3055
3074
  // src/vite-plugin.ts
3056
3075
  var process2 = processImport;
@@ -3410,7 +3429,9 @@ function pushTokens(str, into) {
3410
3429
  if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
3411
3430
  }
3412
3431
  }
3413
- function scanDir(dir, onFile) {
3432
+ var MAX_SCAN_DEPTH = 10;
3433
+ function scanDir(dir, onFile, depth = 0) {
3434
+ if (depth > MAX_SCAN_DEPTH) return;
3414
3435
  let entries;
3415
3436
  try {
3416
3437
  entries = readdirSync(dir);
@@ -3425,7 +3446,7 @@ function scanDir(dir, onFile) {
3425
3446
  const full = join(dir, entry);
3426
3447
  try {
3427
3448
  const st = statSync(full);
3428
- if (st.isDirectory()) scanDir(full, onFile);
3449
+ if (st.isDirectory()) scanDir(full, onFile, depth + 1);
3429
3450
  else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
3430
3451
  } catch (err) {
3431
3452
  warn(`Can't process ${full}: ${err.message}`);
@@ -3443,7 +3464,8 @@ function scanCssFileInto(filePath, into) {
3443
3464
  } catch {
3444
3465
  }
3445
3466
  }
3446
- function scanCssSelectorsInto(dir, into) {
3467
+ function scanCssSelectorsInto(dir, into, depth = 0) {
3468
+ if (depth > MAX_SCAN_DEPTH) return;
3447
3469
  let entries;
3448
3470
  try {
3449
3471
  entries = readdirSync(dir);
@@ -3455,7 +3477,7 @@ function scanCssSelectorsInto(dir, into) {
3455
3477
  const full = join(dir, entry);
3456
3478
  try {
3457
3479
  const st = statSync(full);
3458
- if (st.isDirectory()) scanCssSelectorsInto(full, into);
3480
+ if (st.isDirectory()) scanCssSelectorsInto(full, into, depth + 1);
3459
3481
  else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
3460
3482
  } catch {
3461
3483
  }
@@ -3522,6 +3544,17 @@ function kbach(userConfigOrOptions) {
3522
3544
  function generateCSS() {
3523
3545
  return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
3524
3546
  }
3547
+ function syncMainCSSFile(server) {
3548
+ const active = /* @__PURE__ */ new Set();
3549
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3550
+ for (const tok of tokenCSS.keys()) {
3551
+ if (!active.has(tok)) tokenCSS.delete(tok);
3552
+ }
3553
+ if (mainCSSFile) {
3554
+ const changed = writeKbachToFile(mainCSSFile, generateCSS());
3555
+ if (changed) server.watcher.emit("change", mainCSSFile);
3556
+ }
3557
+ }
3525
3558
  return {
3526
3559
  name: "kbach",
3527
3560
  enforce: "pre",
@@ -3581,6 +3614,37 @@ ${code}`,
3581
3614
  initialScan();
3582
3615
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3583
3616
  },
3617
+ // Vite's `handleHotUpdate` hook only fires for `type === "update"` (plain
3618
+ // file edits) — it's never called for file create/delete, which land on
3619
+ // the watcher's own 'add'/'unlink' events instead (confirmed against
3620
+ // vite's own onHMRUpdate/handleHMRUpdate: the hook loop is gated on
3621
+ // `type === "update"`). Without also listening on those two events here,
3622
+ // creating a new file with new Kbach classes never adds them to
3623
+ // kbach.css, and deleting a file never removes its classes, until a full
3624
+ // dev-server restart. configureServer gives access to the raw watcher so
3625
+ // both cases can share the same sync logic as a plain edit.
3626
+ configureServer(server) {
3627
+ const onAddOrUnlink = (file, isUnlink) => {
3628
+ if (file.includes("node_modules")) return;
3629
+ if (CSS_FILE_RE.test(file)) {
3630
+ if (!isUnlink) scanCssFileInto(file, projectCssClasses);
3631
+ return;
3632
+ }
3633
+ if (!/\.(tsx?|jsx?)$/.test(file)) return;
3634
+ if (isUnlink) {
3635
+ fileTokens.delete(normPath(file));
3636
+ } else {
3637
+ try {
3638
+ processFile(file, readFileSync(file, "utf-8"));
3639
+ } catch {
3640
+ fileTokens.delete(normPath(file));
3641
+ }
3642
+ }
3643
+ syncMainCSSFile(server);
3644
+ };
3645
+ server.watcher.on("add", (file) => onAddOrUnlink(file, false));
3646
+ server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
3647
+ },
3584
3648
  handleHotUpdate({ file, server }) {
3585
3649
  if (file.includes("node_modules")) return;
3586
3650
  if (CSS_FILE_RE.test(file)) {
@@ -3593,15 +3657,7 @@ ${code}`,
3593
3657
  } catch {
3594
3658
  fileTokens.delete(normPath(file));
3595
3659
  }
3596
- const active = /* @__PURE__ */ new Set();
3597
- for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3598
- for (const tok of tokenCSS.keys()) {
3599
- if (!active.has(tok)) tokenCSS.delete(tok);
3600
- }
3601
- if (mainCSSFile) {
3602
- const changed = writeKbachToFile(mainCSSFile, generateCSS());
3603
- if (changed) server.watcher.emit("change", mainCSSFile);
3604
- }
3660
+ syncMainCSSFile(server);
3605
3661
  }
3606
3662
  };
3607
3663
  }