@kbach/react 0.6.25 → 0.6.28

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 }) => {
1298
- 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))` };
1305
- },
1306
- "grid-rows": ({ value, isArbitrary }) => {
1307
- 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))` };
1314
- },
1315
- // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1316
- "grid-flow": ({ value }) => {
1317
- 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;
1327
- },
1328
- // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1329
- "auto-cols": ({ value, isArbitrary }) => {
1330
- 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;
1340
- },
1341
- "auto-rows": ({ value, isArbitrary }) => {
1342
- 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)"
1349
- };
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 }) => {
1028
+ // ── Outline extended (web-only) ───────────────────────────────────────────
1029
+ outline: ({ value, isArbitrary }, { colors }) => {
1395
1030
  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 };
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;
1400
1041
  },
1401
- row: ({ value, isArbitrary }) => {
1042
+ "outline-offset": ({ value, isArbitrary }) => {
1402
1043
  if (!getEffectiveIsWeb()) return null;
1403
- if (!value || value === "auto") return { gridRow: "auto" };
1404
- if (isArbitrary) return { gridRow: value };
1405
- return null;
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;
1406
1047
  },
1407
- // ── Grid alignment (web-only) ──────────────────────────────────────────────
1408
- // place-items is shorthand for align-items + justify-items on a grid container
1409
- "place-items": ({ 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 }) => {
1410
1052
  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;
1053
+ if (value === "reverse") return null;
1054
+ if (!value) return { __divideX: 1 };
1055
+ const n = parseFloat(value);
1056
+ return isNaN(n) ? null : { __divideX: n };
1419
1057
  },
1420
- // place-content is shorthand for align-content + justify-content on a grid container
1421
- "place-content": ({ value }) => {
1058
+ "divide-y": ({ value, isArbitrary }) => {
1422
1059
  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;
1060
+ if (value === "reverse") return null;
1061
+ if (!value) return { __divideY: 1 };
1062
+ const n = parseFloat(value);
1063
+ return isNaN(n) ? null : { __divideY: n };
1434
1064
  },
1435
- // justify-items controls inline-axis alignment of grid items within their cells
1436
- "justify-items": ({ value }) => {
1065
+ divide: ({ value, isArbitrary }, { colors }) => {
1437
1066
  if (!getEffectiveIsWeb()) return null;
1438
- const map = {
1439
- start: "start",
1440
- end: "end",
1441
- center: "center",
1442
- stretch: "stretch"
1067
+ if (isArbitrary) return { __divideColor: value };
1068
+ const styleTokens = {
1069
+ solid: "solid",
1070
+ dashed: "dashed",
1071
+ dotted: "dotted",
1072
+ double: "double",
1073
+ none: "none"
1443
1074
  };
1444
- return map[value] ? { justifyItems: map[value] } : null;
1075
+ if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1076
+ const color = resolveColor(value, colors, false);
1077
+ return color ? { __divideColor: color } : null;
1445
1078
  },
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;
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
+ }
1116
+ return null;
1457
1117
  },
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,412 +1281,629 @@ 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 }) => {
1667
- 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;
1681
- },
1682
- // ── Text decoration advanced (web-only) ───────────────────────────────────
1683
- decoration: ({ value, isArbitrary }, { colors }) => {
1684
- 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;
1709
- },
1710
- "underline-offset": ({ value, isArbitrary }) => {
1711
- if (!getEffectiveIsWeb()) return null;
1712
- if (isArbitrary) return { textUnderlineOffset: value };
1713
- const offsets = {
1714
- auto: "auto",
1715
- "0": "0px",
1716
- "1": "1px",
1717
- "2": "2px",
1718
- "4": "4px",
1719
- "8": "8px"
1720
- };
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 }) => {
1284
+ // ── Mix / background blend mode (web-only) ───────────────────────────────
1285
+ "mix-blend": ({ value }) => {
1728
1286
  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 };
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;
1754
1307
  },
1755
- // ── Outline extended (web-only) ───────────────────────────────────────────
1756
- outline: ({ value, isArbitrary }, { colors }) => {
1308
+ "bg-blend": ({ value }) => {
1757
1309
  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;
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;
1768
1329
  },
1769
- "outline-offset": ({ value, isArbitrary }) => {
1330
+ // ── Will-change (web-only) ─────────────────────────────────────────────────
1331
+ "will-change": ({ value, isArbitrary }) => {
1770
1332
  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;
1333
+ if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1334
+ const presets = {
1335
+ auto: "auto",
1336
+ scroll: "scroll-position",
1337
+ contents: "contents",
1338
+ transform: "transform"
1339
+ };
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, wrap, grow/shrink, and alignment (both web and native) —
1369
+ // every one of these is meaningless without the element also being a flex
1370
+ // container. React Native Views are ALWAYS flex containers by default, so
1371
+ // on native this was already true for free; on web a plain <div> defaults
1372
+ // to display:block, so e.g. `items-center` alone silently did nothing.
1373
+ // Folding `display: 'flex'` into these on web closes that native/web gap —
1374
+ // `flex-1 items-center justify-center` now behaves the same on both,
1375
+ // without also needing a separate `flex` class.
1376
+ "flex-row": { flexDirection: "row", ...web ? { display: "flex" } : {} },
1377
+ "flex-col": { flexDirection: "column", ...web ? { display: "flex" } : {} },
1378
+ "flex-row-reverse": { flexDirection: "row-reverse", ...web ? { display: "flex" } : {} },
1379
+ "flex-col-reverse": { flexDirection: "column-reverse", ...web ? { display: "flex" } : {} },
1380
+ // Flex wrap (both)
1381
+ "flex-wrap": { flexWrap: "wrap", ...web ? { display: "flex" } : {} },
1382
+ "flex-wrap-reverse": { flexWrap: "wrap-reverse", ...web ? { display: "flex" } : {} },
1383
+ "flex-nowrap": { flexWrap: "nowrap", ...web ? { display: "flex" } : {} },
1384
+ // Flex grow / shrink (both) — deliberately NOT folding in display:flex
1385
+ // like the container-level properties above. flex-grow/flex-shrink are
1386
+ // item-level: they already take effect purely via the PARENT already
1387
+ // being a flex container, with zero dependency on this element's own
1388
+ // display. Forcing display:flex here would instead be an unrelated side
1389
+ // effect on THIS element's own children (turning what may be an ordinary
1390
+ // block stack into a flex row) — exactly the kind of surprise the
1391
+ // container-level properties above can never cause, since those are
1392
+ // no-ops without display:flex to begin with.
1393
+ "flex-grow": { flexGrow: 1 },
1394
+ "flex-grow-0": { flexGrow: 0 },
1395
+ "flex-shrink": { flexShrink: 1 },
1396
+ "flex-shrink-0": { flexShrink: 0 },
1397
+ // Align items (both)
1398
+ "items-start": { alignItems: "flex-start", ...web ? { display: "flex" } : {} },
1399
+ "items-end": { alignItems: "flex-end", ...web ? { display: "flex" } : {} },
1400
+ "items-center": { alignItems: "center", ...web ? { display: "flex" } : {} },
1401
+ "items-baseline": { alignItems: "baseline", ...web ? { display: "flex" } : {} },
1402
+ "items-stretch": { alignItems: "stretch", ...web ? { display: "flex" } : {} },
1403
+ // Justify content (both)
1404
+ "justify-start": { justifyContent: "flex-start", ...web ? { display: "flex" } : {} },
1405
+ "justify-end": { justifyContent: "flex-end", ...web ? { display: "flex" } : {} },
1406
+ "justify-center": { justifyContent: "center", ...web ? { display: "flex" } : {} },
1407
+ "justify-between": { justifyContent: "space-between", ...web ? { display: "flex" } : {} },
1408
+ "justify-around": { justifyContent: "space-around", ...web ? { display: "flex" } : {} },
1409
+ "justify-evenly": { justifyContent: "space-evenly", ...web ? { display: "flex" } : {} },
1410
+ // Align content (both)
1411
+ "content-start": { alignContent: "flex-start", ...web ? { display: "flex" } : {} },
1412
+ "content-end": { alignContent: "flex-end", ...web ? { display: "flex" } : {} },
1413
+ "content-center": { alignContent: "center", ...web ? { display: "flex" } : {} },
1414
+ "content-between": { alignContent: "space-between", ...web ? { display: "flex" } : {} },
1415
+ "content-around": { alignContent: "space-around", ...web ? { display: "flex" } : {} },
1416
+ "content-evenly": { alignContent: "space-evenly", ...web ? { display: "flex" } : {} },
1417
+ "content-stretch": { alignContent: "stretch", ...web ? { display: "flex" } : {} },
1418
+ // Align self (both)
1419
+ "self-auto": { alignSelf: "auto" },
1420
+ "self-start": { alignSelf: "flex-start" },
1421
+ "self-end": { alignSelf: "flex-end" },
1422
+ "self-center": { alignSelf: "center" },
1423
+ "self-stretch": { alignSelf: "stretch" },
1424
+ "self-baseline": { alignSelf: "baseline" },
1425
+ // Text align (both)
1426
+ "text-left": { textAlign: "left" },
1427
+ "text-right": { textAlign: "right" },
1428
+ "text-center": { textAlign: "center" },
1429
+ "text-justify": { textAlign: "justify" },
1430
+ // Font weight shortcuts (both)
1431
+ "font-thin": { fontWeight: "100" },
1432
+ "font-extralight": { fontWeight: "200" },
1433
+ "font-light": { fontWeight: "300" },
1434
+ "font-normal": { fontWeight: "400" },
1435
+ "font-medium": { fontWeight: "500" },
1436
+ "font-semibold": { fontWeight: "600" },
1437
+ "font-bold": { fontWeight: "700" },
1438
+ "font-extrabold": { fontWeight: "800" },
1439
+ "font-black": { fontWeight: "900" },
1440
+ // Text decoration (overline is web-only)
1441
+ underline: { textDecorationLine: "underline" },
1442
+ overline: web ? { textDecorationLine: "overline" } : null,
1443
+ "line-through": { textDecorationLine: "line-through" },
1444
+ "no-underline": { textDecorationLine: "none" },
1445
+ // Text transform (both)
1446
+ uppercase: { textTransform: "uppercase" },
1447
+ lowercase: { textTransform: "lowercase" },
1448
+ capitalize: { textTransform: "capitalize" },
1449
+ "normal-case": { textTransform: "none" },
1450
+ // Font style (both)
1451
+ italic: { fontStyle: "italic" },
1452
+ "non-italic": { fontStyle: "normal" },
1453
+ // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
1454
+ relative: { position: "relative" },
1455
+ absolute: { position: "absolute" },
1456
+ fixed: web ? { position: "fixed" } : null,
1457
+ sticky: web ? { position: "sticky" } : null,
1458
+ static: web ? { position: "static" } : null,
1459
+ // Visibility (no `visibility` prop on native; use opacity instead)
1460
+ visible: web ? { visibility: "visible" } : null,
1461
+ invisible: web ? { visibility: "hidden" } : { opacity: 0 },
1462
+ // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
1463
+ "overflow-hidden": { overflow: "hidden" },
1464
+ "overflow-visible": { overflow: "visible" },
1465
+ "overflow-scroll": { overflow: "scroll" },
1466
+ "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
1467
+ // overflowX / overflowY are web-only
1468
+ "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
1469
+ "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
1470
+ "overflow-x-auto": web ? { overflowX: "auto" } : null,
1471
+ "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
1472
+ "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
1473
+ "overflow-y-auto": web ? { overflowY: "auto" } : null,
1474
+ // Object fit (web-only; use resizeMode prop on RN Image)
1475
+ "object-contain": web ? { objectFit: "contain" } : null,
1476
+ "object-cover": web ? { objectFit: "cover" } : null,
1477
+ "object-fill": web ? { objectFit: "fill" } : null,
1478
+ "object-none": web ? { objectFit: "none" } : null,
1479
+ "object-scale-down": web ? { objectFit: "scale-down" } : null,
1480
+ // Border style (both)
1481
+ "border-solid": { borderStyle: "solid" },
1482
+ "border-dashed": { borderStyle: "dashed" },
1483
+ "border-dotted": { borderStyle: "dotted" },
1484
+ "border-none": { borderWidth: 0 },
1485
+ // Border sides default width (both)
1486
+ "border-t": { borderTopWidth: 1 },
1487
+ "border-r": { borderRightWidth: 1 },
1488
+ "border-b": { borderBottomWidth: 1 },
1489
+ "border-l": { borderLeftWidth: 1 },
1490
+ // Cursor (web-only)
1491
+ "cursor-auto": web ? { cursor: "auto" } : null,
1492
+ "cursor-default": web ? { cursor: "default" } : null,
1493
+ "cursor-pointer": web ? { cursor: "pointer" } : null,
1494
+ "cursor-wait": web ? { cursor: "wait" } : null,
1495
+ "cursor-text": web ? { cursor: "text" } : null,
1496
+ "cursor-move": web ? { cursor: "move" } : null,
1497
+ "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
1498
+ // User select (web-only)
1499
+ "select-none": web ? { userSelect: "none" } : null,
1500
+ "select-text": web ? { userSelect: "text" } : null,
1501
+ "select-all": web ? { userSelect: "all" } : null,
1502
+ "select-auto": web ? { userSelect: "auto" } : null,
1503
+ // Pointer events (both)
1504
+ "pointer-events-none": { pointerEvents: "none" },
1505
+ "pointer-events-auto": { pointerEvents: "auto" },
1506
+ // Whitespace (web-only; RN Text uses numberOfLines prop instead)
1507
+ "whitespace-normal": web ? { whiteSpace: "normal" } : null,
1508
+ "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
1509
+ "whitespace-pre": web ? { whiteSpace: "pre" } : null,
1510
+ "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
1511
+ "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
1512
+ // Word break (web-only)
1513
+ "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
1514
+ "break-words": web ? { overflowWrap: "break-word" } : null,
1515
+ "break-all": web ? { wordBreak: "break-all" } : null,
1516
+ // Misc web-only
1517
+ truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
1518
+ "box-border": web ? { boxSizing: "border-box" } : null,
1519
+ "box-content": web ? { boxSizing: "content-box" } : null,
1520
+ "appearance-none": web ? { appearance: "none" } : null,
1521
+ "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
1522
+ outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
1523
+ resize: web ? { resize: "both" } : null,
1524
+ "resize-none": web ? { resize: "none" } : null,
1525
+ "resize-y": web ? { resize: "vertical" } : null,
1526
+ "resize-x": web ? { resize: "horizontal" } : null,
1527
+ antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
1528
+ "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
1529
+ "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
1530
+ "sr-only": web ? {
1531
+ position: "absolute",
1532
+ width: 1,
1533
+ height: 1,
1534
+ padding: 0,
1535
+ margin: -1,
1536
+ overflow: "hidden",
1537
+ clip: "rect(0,0,0,0)",
1538
+ whiteSpace: "nowrap",
1539
+ border: 0
1540
+ } : null,
1541
+ "not-sr-only": web ? {
1542
+ position: "static",
1543
+ width: "auto",
1544
+ height: "auto",
1545
+ padding: 0,
1546
+ margin: 0,
1547
+ overflow: "visible",
1548
+ clip: "auto",
1549
+ whiteSpace: "normal"
1550
+ } : null,
1551
+ // Lists (web-only)
1552
+ "list-none": web ? { listStyleType: "none" } : null,
1553
+ "list-disc": web ? { listStyleType: "disc" } : null,
1554
+ "list-decimal": web ? { listStyleType: "decimal" } : null,
1555
+ // Background clip (web-only)
1556
+ "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
1557
+ "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
1558
+ "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
1559
+ "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
1560
+ // Background image
1561
+ "bg-none": web ? { backgroundImage: "none" } : null,
1562
+ // Background size (web-only)
1563
+ "bg-auto": web ? { backgroundSize: "auto" } : null,
1564
+ "bg-cover": web ? { backgroundSize: "cover" } : null,
1565
+ "bg-contain": web ? { backgroundSize: "contain" } : null,
1566
+ // Background position (web-only)
1567
+ "bg-center": web ? { backgroundPosition: "center" } : null,
1568
+ "bg-top": web ? { backgroundPosition: "top" } : null,
1569
+ "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
1570
+ "bg-left": web ? { backgroundPosition: "left" } : null,
1571
+ "bg-right": web ? { backgroundPosition: "right" } : null,
1572
+ "bg-left-top": web ? { backgroundPosition: "left top" } : null,
1573
+ "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
1574
+ "bg-right-top": web ? { backgroundPosition: "right top" } : null,
1575
+ "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
1576
+ // Background repeat (web-only)
1577
+ "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
1578
+ "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
1579
+ "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
1580
+ "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
1581
+ "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
1582
+ "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
1583
+ // Background attachment (web-only)
1584
+ "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
1585
+ "bg-local": web ? { backgroundAttachment: "local" } : null,
1586
+ "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
1587
+ // Object position (web-only)
1588
+ "object-center": web ? { objectPosition: "center" } : null,
1589
+ "object-top": web ? { objectPosition: "top" } : null,
1590
+ "object-bottom": web ? { objectPosition: "bottom" } : null,
1591
+ "object-left": web ? { objectPosition: "left" } : null,
1592
+ "object-right": web ? { objectPosition: "right" } : null,
1593
+ "object-left-top": web ? { objectPosition: "left top" } : null,
1594
+ "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1595
+ "object-right-top": web ? { objectPosition: "right top" } : null,
1596
+ "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1597
+ // Table (web-only)
1598
+ table: web ? { display: "table" } : null,
1599
+ "table-auto": web ? { tableLayout: "auto" } : null,
1600
+ "table-fixed": web ? { tableLayout: "fixed" } : null,
1601
+ "caption-top": web ? { captionSide: "top" } : null,
1602
+ "caption-bottom": web ? { captionSide: "bottom" } : null,
1603
+ "border-collapse": web ? { borderCollapse: "collapse" } : null,
1604
+ "border-separate": web ? { borderCollapse: "separate" } : null,
1605
+ // List style position (web-only)
1606
+ "list-inside": web ? { listStylePosition: "inside" } : null,
1607
+ "list-outside": web ? { listStylePosition: "outside" } : null,
1608
+ // Font variant numeric (web-only)
1609
+ "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1610
+ ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1611
+ "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1612
+ "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1613
+ "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1614
+ "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1615
+ "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1616
+ "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1617
+ "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1618
+ // Isolation (web-only)
1619
+ isolate: web ? { isolation: "isolate" } : null,
1620
+ "isolation-auto": web ? { isolation: "auto" } : null,
1621
+ // Backface visibility (both iOS and Android)
1622
+ "backface-visible": { backfaceVisibility: "visible" },
1623
+ "backface-hidden": { backfaceVisibility: "hidden" },
1624
+ // CSS filters — standalone = default/full effect (web-only)
1625
+ grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1626
+ invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1627
+ sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1628
+ // Divide none
1629
+ "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1630
+ // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1631
+ group: web ? {} : null,
1632
+ peer: web ? {} : null,
1633
+ // Text-wrap (web-only)
1634
+ "text-wrap": web ? { textWrap: "wrap" } : null,
1635
+ "text-nowrap": web ? { textWrap: "nowrap" } : null,
1636
+ "text-balance": web ? { textWrap: "balance" } : null,
1637
+ "text-pretty": web ? { textWrap: "pretty" } : null,
1638
+ // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1639
+ // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1640
+ // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1641
+ // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1642
+ // is unchanged since there's no dynamic chrome to account for.
1643
+ // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1644
+ "w-screen": web ? { width: "100dvw" } : null,
1645
+ "min-h-screen": { minHeight: "100dvh" },
1646
+ "max-h-screen": { maxHeight: "100dvh" },
1647
+ "min-w-screen": web ? { minWidth: "100dvw" } : null,
1648
+ "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1649
+ // max-w named container sizes (mirrors Tailwind's container scale)
1650
+ "max-w-none": { maxWidth: "none" },
1651
+ "max-w-xs": { maxWidth: 320 },
1652
+ "max-w-sm": { maxWidth: 384 },
1653
+ "max-w-md": { maxWidth: 448 },
1654
+ "max-w-lg": { maxWidth: 512 },
1655
+ "max-w-xl": { maxWidth: 576 },
1656
+ "max-w-2xl": { maxWidth: 672 },
1657
+ "max-w-3xl": { maxWidth: 768 },
1658
+ "max-w-4xl": { maxWidth: 896 },
1659
+ "max-w-5xl": { maxWidth: 1024 },
1660
+ "max-w-6xl": { maxWidth: 1152 },
1661
+ "max-w-7xl": { maxWidth: 1280 },
1662
+ "max-w-prose": web ? { maxWidth: "65ch" } : null,
1663
+ // Extended cursors (web-only)
1664
+ "cursor-grab": web ? { cursor: "grab" } : null,
1665
+ "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1666
+ "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1667
+ "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1668
+ "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1669
+ "cursor-help": web ? { cursor: "help" } : null,
1670
+ "cursor-none": web ? { cursor: "none" } : null,
1671
+ // Overflow clip (web-only)
1672
+ "overflow-clip": web ? { overflow: "clip" } : null,
1673
+ "overflow-x-clip": web ? { overflowX: "clip" } : null,
1674
+ "overflow-y-clip": web ? { overflowY: "clip" } : null,
1675
+ // Scroll behavior (web-only)
1676
+ "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1677
+ "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1678
+ // Float (web-only)
1679
+ "float-left": web ? { float: "left" } : null,
1680
+ "float-right": web ? { float: "right" } : null,
1681
+ "float-start": web ? { float: "inline-start" } : null,
1682
+ "float-end": web ? { float: "inline-end" } : null,
1683
+ "float-none": web ? { float: "none" } : null,
1684
+ // Clear (web-only)
1685
+ "clear-left": web ? { clear: "left" } : null,
1686
+ "clear-right": web ? { clear: "right" } : null,
1687
+ "clear-both": web ? { clear: "both" } : null,
1688
+ "clear-start": web ? { clear: "inline-start" } : null,
1689
+ "clear-end": web ? { clear: "inline-end" } : null,
1690
+ "clear-none": web ? { clear: "none" } : null,
1691
+ // Vertical align (web-only)
1692
+ "align-baseline": web ? { verticalAlign: "baseline" } : null,
1693
+ "align-top": web ? { verticalAlign: "top" } : null,
1694
+ "align-middle": web ? { verticalAlign: "middle" } : null,
1695
+ "align-bottom": web ? { verticalAlign: "bottom" } : null,
1696
+ "align-text-top": web ? { verticalAlign: "text-top" } : null,
1697
+ "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1698
+ "align-sub": web ? { verticalAlign: "sub" } : null,
1699
+ "align-super": web ? { verticalAlign: "super" } : null,
1700
+ // Touch action (web-only)
1701
+ "touch-auto": web ? { touchAction: "auto" } : null,
1702
+ "touch-none": web ? { touchAction: "none" } : null,
1703
+ "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1704
+ "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1705
+ "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1706
+ "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1707
+ "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1708
+ "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1709
+ "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1710
+ "touch-manipulation": web ? { touchAction: "manipulation" } : null
1711
+ };
1712
+ }
1713
+ function getStandalone() {
1714
+ if (!_standalone) _standalone = buildStandalone();
1715
+ return _standalone;
1716
+ }
1717
+ var layoutResolvers = {
1718
+ // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1719
+ grid: ({ value, isArbitrary }) => {
1720
+ if (!value) return null;
1721
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1722
+ const n = parseInt(value, 10);
1723
+ if (isNaN(n) || n < 1 || n > 12) return null;
1724
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1774
1725
  },
1775
- // ── Columns (web-only) ─────────────────────────────────────────────────────
1776
- columns: ({ value, isArbitrary }) => {
1726
+ // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1727
+ "grid-cols": ({ value, isArbitrary }) => {
1777
1728
  if (!getEffectiveIsWeb()) return null;
1778
- if (isArbitrary) return { columnCount: value };
1779
- if (value === "auto") return { columnCount: "auto" };
1729
+ if (!value) return null;
1730
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1731
+ if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1780
1732
  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;
1733
+ if (isNaN(n) || n < 1 || n > 12) return null;
1734
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1798
1735
  },
1799
- // ── Mix / background blend mode (web-only) ───────────────────────────────
1800
- "mix-blend": ({ value }) => {
1736
+ "grid-rows": ({ value, isArbitrary }) => {
1801
1737
  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;
1738
+ if (!value) return null;
1739
+ if (isArbitrary) return { gridTemplateRows: value };
1740
+ if (value === "none") return { gridTemplateRows: "none" };
1741
+ const n = parseInt(value, 10);
1742
+ if (isNaN(n) || n < 1) return null;
1743
+ return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1822
1744
  },
1823
- "bg-blend": ({ value }) => {
1745
+ // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1746
+ "grid-flow": ({ value }) => {
1824
1747
  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;
1748
+ const flows = {
1749
+ row: "row",
1750
+ col: "column",
1751
+ dense: "dense",
1752
+ "row-dense": "row dense",
1753
+ "col-dense": "column dense"
1754
+ };
1755
+ const flow = flows[value];
1756
+ return flow ? { gridAutoFlow: flow } : null;
1844
1757
  },
1845
- // ── Will-change (web-only) ─────────────────────────────────────────────────
1846
- "will-change": ({ value, isArbitrary }) => {
1758
+ // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1759
+ "auto-cols": ({ value, isArbitrary }) => {
1847
1760
  if (!getEffectiveIsWeb()) return null;
1848
- if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1761
+ if (isArbitrary) return { gridAutoColumns: value };
1849
1762
  const presets = {
1850
1763
  auto: "auto",
1851
- scroll: "scroll-position",
1852
- contents: "contents",
1853
- transform: "transform"
1764
+ min: "min-content",
1765
+ max: "max-content",
1766
+ fr: "minmax(0, 1fr)"
1854
1767
  };
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 };
1768
+ const v = presets[value];
1769
+ return v ? { gridAutoColumns: v } : null;
1873
1770
  },
1874
- divide: ({ value, isArbitrary }, { colors }) => {
1771
+ "auto-rows": ({ value, isArbitrary }) => {
1875
1772
  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 }
1773
+ if (isArbitrary) return { gridAutoRows: value };
1774
+ const presets = {
1775
+ auto: "auto",
1776
+ min: "min-content",
1777
+ max: "max-content",
1778
+ fr: "minmax(0, 1fr)"
1907
1779
  };
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;
1780
+ const v = presets[value];
1781
+ return v ? { gridAutoRows: v } : null;
1920
1782
  },
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 }] };
1783
+ // ── Grid column placement (web-only) ──────────────────────────────────────
1784
+ "col-span": ({ value }) => {
1785
+ if (!getEffectiveIsWeb()) return null;
1786
+ if (value === "full") return { gridColumn: "1 / -1" };
1787
+ const n = parseInt(value, 10);
1788
+ return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1932
1789
  },
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;
1790
+ "col-start": ({ value, isArbitrary }) => {
1791
+ if (!getEffectiveIsWeb()) return null;
1792
+ if (value === "auto") return { gridColumnStart: "auto" };
1793
+ if (isArbitrary) return { gridColumnStart: value };
1794
+ const n = parseInt(value, 10);
1795
+ return isNaN(n) ? null : { gridColumnStart: n };
1938
1796
  },
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;
1797
+ "col-end": ({ value, isArbitrary }) => {
1798
+ if (!getEffectiveIsWeb()) return null;
1799
+ if (value === "auto") return { gridColumnEnd: "auto" };
1800
+ if (isArbitrary) return { gridColumnEnd: value };
1801
+ const n = parseInt(value, 10);
1802
+ return isNaN(n) ? null : { gridColumnEnd: n };
1943
1803
  },
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) => {
1804
+ col: ({ value, isArbitrary }) => {
1949
1805
  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;
1806
+ if (!value || value === "auto") return { gridColumn: "auto" };
1807
+ if (isArbitrary) return { gridColumn: value };
1808
+ return null;
1954
1809
  },
1955
- // ── Line-clamp (web-only) ─────────────────────────────────────────────────
1956
- "line-clamp": ({ value }) => {
1810
+ // ── Grid row placement (web-only) ─────────────────────────────────────────
1811
+ "row-span": ({ value }) => {
1957
1812
  if (!getEffectiveIsWeb()) return null;
1958
- if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
1813
+ if (value === "full") return { gridRow: "1 / -1" };
1959
1814
  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
1815
+ return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1816
+ },
1817
+ "row-start": ({ value, isArbitrary }) => {
1818
+ if (!getEffectiveIsWeb()) return null;
1819
+ if (value === "auto") return { gridRowStart: "auto" };
1820
+ if (isArbitrary) return { gridRowStart: value };
1821
+ const n = parseInt(value, 10);
1822
+ return isNaN(n) ? null : { gridRowStart: n };
1823
+ },
1824
+ "row-end": ({ value, isArbitrary }) => {
1825
+ if (!getEffectiveIsWeb()) return null;
1826
+ if (value === "auto") return { gridRowEnd: "auto" };
1827
+ if (isArbitrary) return { gridRowEnd: value };
1828
+ const n = parseInt(value, 10);
1829
+ return isNaN(n) ? null : { gridRowEnd: n };
1830
+ },
1831
+ row: ({ value, isArbitrary }) => {
1832
+ if (!getEffectiveIsWeb()) return null;
1833
+ if (!value || value === "auto") return { gridRow: "auto" };
1834
+ if (isArbitrary) return { gridRow: value };
1835
+ return null;
1836
+ },
1837
+ // ── Grid alignment (web-only) ──────────────────────────────────────────────
1838
+ // place-items is shorthand for align-items + justify-items on a grid container
1839
+ "place-items": ({ value }) => {
1840
+ if (!getEffectiveIsWeb()) return null;
1841
+ const map = {
1842
+ start: "start",
1843
+ end: "end",
1844
+ center: "center",
1845
+ stretch: "stretch",
1846
+ baseline: "baseline"
1966
1847
  };
1848
+ return map[value] ? { placeItems: map[value] } : null;
1967
1849
  },
1968
- // ── Caret color (web-only) ────────────────────────────────────────────────
1969
- caret: ({ value, isArbitrary }, { colors }) => {
1850
+ // place-content is shorthand for align-content + justify-content on a grid container
1851
+ "place-content": ({ value }) => {
1970
1852
  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;
1853
+ const map = {
1854
+ start: "start",
1855
+ end: "end",
1856
+ center: "center",
1857
+ stretch: "stretch",
1858
+ between: "space-between",
1859
+ around: "space-around",
1860
+ evenly: "space-evenly",
1861
+ baseline: "baseline"
1862
+ };
1863
+ return map[value] ? { placeContent: map[value] } : null;
1974
1864
  },
1975
- // ── Accent color (web-only) ───────────────────────────────────────────────
1976
- accent: ({ value, isArbitrary }, { colors }) => {
1865
+ // justify-items controls inline-axis alignment of grid items within their cells
1866
+ "justify-items": ({ value }) => {
1977
1867
  if (!getEffectiveIsWeb()) return null;
1978
- if (value === "auto") return { accentColor: "auto" };
1979
- const color = resolveColor(value, colors, isArbitrary);
1980
- return color ? { accentColor: color } : null;
1868
+ const map = {
1869
+ start: "start",
1870
+ end: "end",
1871
+ center: "center",
1872
+ stretch: "stretch"
1873
+ };
1874
+ return map[value] ? { justifyItems: map[value] } : null;
1981
1875
  },
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 }) => {
1876
+ // place-self is shorthand for align-self + justify-self on a grid item
1877
+ "place-self": ({ value }) => {
1989
1878
  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 };
1879
+ const map = {
1880
+ auto: "auto",
1881
+ start: "start",
1882
+ end: "end",
1883
+ center: "center",
1884
+ stretch: "stretch"
1885
+ };
1886
+ return map[value] ? { placeSelf: map[value] } : null;
2000
1887
  },
2001
- fill: ({ value, isArbitrary }, { colors }) => {
1888
+ // justify-self controls inline-axis self-alignment of a grid item
1889
+ "justify-self": ({ value }) => {
2002
1890
  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;
1891
+ const map = {
1892
+ auto: "auto",
1893
+ start: "start",
1894
+ end: "end",
1895
+ center: "center",
1896
+ stretch: "stretch"
1897
+ };
1898
+ return map[value] ? { justifySelf: map[value] } : null;
2008
1899
  },
2009
1900
  // ── Flex ───────────────────────────────────────────────────────────────────
1901
+ // flex-1/flex-auto/flex-none/grow/shrink are item-level sizing properties —
1902
+ // deliberately NOT implying display:flex here. See the "Flex grow / shrink"
1903
+ // comment in the standalone table above for why: these already work via the
1904
+ // PARENT's flex context regardless of this element's own display, so
1905
+ // forcing display:flex here would only be a surprising, unrelated side
1906
+ // effect on this element's OWN children.
2010
1907
  flex: ({ value, isArbitrary }, { flex }) => {
2011
1908
  if (!value) return { display: "flex" };
2012
1909
  if (isArbitrary) {
@@ -2025,10 +1922,6 @@ var RESOLVERS = {
2025
1922
  const n = parseFloat(value);
2026
1923
  return isNaN(n) ? null : { flex: n };
2027
1924
  },
2028
- basis: ({ value, isArbitrary }, { spacing }) => {
2029
- const v = resolveSizing(value, spacing, isArbitrary);
2030
- return v !== null ? { flexBasis: v } : null;
2031
- },
2032
1925
  grow: ({ value }, _) => {
2033
1926
  if (!value) return { flexGrow: 1 };
2034
1927
  const n = parseFloat(value);
@@ -2043,72 +1936,132 @@ var RESOLVERS = {
2043
1936
  const n = parseInt(value, 10);
2044
1937
  return isNaN(n) ? null : { order: negative ? -n : n };
2045
1938
  },
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
1939
  // ── Z-index ────────────────────────────────────────────────────────────────
2089
1940
  z: ({ value, isArbitrary }, { zIndex }) => {
2090
1941
  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 };
1942
+ const n2 = parseInt(value);
1943
+ return isNaN(n2) ? null : { zIndex: n2 };
1944
+ }
1945
+ const v = zIndex[value];
1946
+ if (v !== void 0) {
1947
+ if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
1948
+ return { zIndex: v };
1949
+ }
1950
+ const n = parseInt(value);
1951
+ return isNaN(n) ? null : { zIndex: n };
1952
+ },
1953
+ // ── Aspect ratio ───────────────────────────────────────────────────────────
1954
+ aspect: ({ value, isArbitrary }) => {
1955
+ if (isArbitrary) return { aspectRatio: value };
1956
+ const presets = { auto: "auto", square: 1, video: 16 / 9 };
1957
+ if (!(value in presets)) return null;
1958
+ const v = presets[value];
1959
+ if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
1960
+ return { aspectRatio: v };
1961
+ },
1962
+ // ── Columns (web-only) ─────────────────────────────────────────────────────
1963
+ columns: ({ value, isArbitrary }) => {
1964
+ if (!getEffectiveIsWeb()) return null;
1965
+ if (isArbitrary) return { columnCount: value };
1966
+ if (value === "auto") return { columnCount: "auto" };
1967
+ const n = parseInt(value, 10);
1968
+ if (!isNaN(n)) return { columnCount: n };
1969
+ const widths = {
1970
+ "3xs": "16rem",
1971
+ "2xs": "18rem",
1972
+ xs: "20rem",
1973
+ sm: "24rem",
1974
+ md: "28rem",
1975
+ lg: "32rem",
1976
+ xl: "36rem",
1977
+ "2xl": "42rem",
1978
+ "3xl": "48rem",
1979
+ "4xl": "56rem",
1980
+ "5xl": "64rem",
1981
+ "6xl": "72rem",
1982
+ "7xl": "80rem"
1983
+ };
1984
+ return widths[value] ? { columnWidth: widths[value] } : null;
1985
+ }
1986
+ };
1987
+
1988
+ // src/core/resolvers/typography.ts
1989
+ function resolveFontSize(value, fontSizes, isArbitrary) {
1990
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
1991
+ return fontSizes[value] ?? null;
1992
+ }
1993
+ var typographyResolvers = {
1994
+ // ── Text ───────────────────────────────────────────────────────────────────
1995
+ text: ({ value, isArbitrary }, { colors, fontSize }) => {
1996
+ const size = resolveFontSize(value, fontSize, false);
1997
+ if (!isArbitrary && size !== null) return { fontSize: size };
1998
+ if (isArbitrary) {
1999
+ if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
2000
+ return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
2001
+ }
2002
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
2098
2003
  }
2099
- const n = parseInt(value);
2100
- return isNaN(n) ? null : { zIndex: n };
2004
+ const color = resolveColor(value, colors, false);
2005
+ if (!color) return null;
2006
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
2101
2007
  },
2102
- // ── Opacity ────────────────────────────────────────────────────────────────
2103
- opacity: ({ value, isArbitrary }, { opacity }) => {
2008
+ "text-opacity": ({ value, isArbitrary }, _) => {
2009
+ if (!getEffectiveIsWeb()) return null;
2010
+ const n = parseFloat(value);
2011
+ if (isNaN(n)) return null;
2012
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
2013
+ return { "--text-opacity": v };
2014
+ },
2015
+ // ── Text decoration advanced (web-only) ───────────────────────────────────
2016
+ decoration: ({ value, isArbitrary }, { colors }) => {
2017
+ if (!getEffectiveIsWeb()) return null;
2104
2018
  if (isArbitrary) {
2105
- const v2 = parseFloat(value);
2106
- return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2019
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
2020
+ return { textDecorationColor: value };
2107
2021
  }
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 };
2022
+ const thickTokens = {
2023
+ auto: "auto",
2024
+ "from-font": "from-font",
2025
+ "0": "0px",
2026
+ "1": "1px",
2027
+ "2": "2px",
2028
+ "4": "4px",
2029
+ "8": "8px"
2030
+ };
2031
+ if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
2032
+ const styleTokens = {
2033
+ solid: "solid",
2034
+ dashed: "dashed",
2035
+ dotted: "dotted",
2036
+ double: "double",
2037
+ wavy: "wavy"
2038
+ };
2039
+ if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
2040
+ const color = resolveColor(value, colors, false);
2041
+ return color ? { textDecorationColor: color } : null;
2042
+ },
2043
+ "underline-offset": ({ value, isArbitrary }) => {
2044
+ if (!getEffectiveIsWeb()) return null;
2045
+ if (isArbitrary) return { textUnderlineOffset: value };
2046
+ const offsets = {
2047
+ auto: "auto",
2048
+ "0": "0px",
2049
+ "1": "1px",
2050
+ "2": "2px",
2051
+ "4": "4px",
2052
+ "8": "8px"
2053
+ };
2054
+ return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2055
+ },
2056
+ // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
2057
+ // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
2058
+ // the literal string 'you can see it' quotes included), so it passes straight through as a
2059
+ // valid `content: '*'` CSS value with no extra wrapping needed.
2060
+ content: ({ value, isArbitrary }) => {
2061
+ if (!getEffectiveIsWeb()) return null;
2062
+ if (isArbitrary) return { content: value };
2063
+ const presets = { none: "none" };
2064
+ return value in presets ? { content: presets[value] } : null;
2112
2065
  },
2113
2066
  // ── Font ───────────────────────────────────────────────────────────────────
2114
2067
  font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
@@ -2134,10 +2087,57 @@ var RESOLVERS = {
2134
2087
  const v = letterSpacing[value];
2135
2088
  return v !== void 0 ? { letterSpacing: v } : null;
2136
2089
  },
2137
- // ── Shadow ─────────────────────────────────────────────────────────────────
2138
- shadow: ({ value }, { shadow }) => {
2139
- const key = value === "" ? "DEFAULT" : value;
2140
- return shadow[key] ?? null;
2090
+ // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2091
+ "line-clamp": ({ value }) => {
2092
+ if (!getEffectiveIsWeb()) return null;
2093
+ if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2094
+ const n = parseInt(value, 10);
2095
+ if (isNaN(n) || n < 1) return null;
2096
+ return {
2097
+ overflow: "hidden",
2098
+ display: "-webkit-box",
2099
+ WebkitBoxOrient: "vertical",
2100
+ WebkitLineClamp: n
2101
+ };
2102
+ }
2103
+ };
2104
+
2105
+ // src/core/resolvers/transform.ts
2106
+ var transformResolvers = {
2107
+ // ── Transform extras (web-only) ───────────────────────────────────────────
2108
+ "skew-x": ({ value, negative, isArbitrary }) => {
2109
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2110
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2111
+ return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2112
+ },
2113
+ "skew-y": ({ value, negative, isArbitrary }) => {
2114
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2115
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2116
+ return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
2117
+ },
2118
+ // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
2119
+ transform: ({ value, isArbitrary }) => {
2120
+ if (!getEffectiveIsWeb()) return null;
2121
+ if (isArbitrary) return { transform: value.replace(/_/g, " ") };
2122
+ if (value === "none") return { transform: "none" };
2123
+ return null;
2124
+ },
2125
+ // Transform origin
2126
+ origin: ({ value, isArbitrary }) => {
2127
+ if (!getEffectiveIsWeb()) return null;
2128
+ if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
2129
+ const origins = {
2130
+ center: "center",
2131
+ top: "top",
2132
+ "top-right": "top right",
2133
+ right: "right",
2134
+ "bottom-right": "bottom right",
2135
+ bottom: "bottom",
2136
+ "bottom-left": "bottom left",
2137
+ left: "left",
2138
+ "top-left": "top left"
2139
+ };
2140
+ return origins[value] ? { transformOrigin: origins[value] } : null;
2141
2141
  },
2142
2142
  // ── Scale ──────────────────────────────────────────────────────────────────
2143
2143
  // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
@@ -2168,35 +2168,86 @@ var RESOLVERS = {
2168
2168
  const finalDeg = negative ? -deg : deg;
2169
2169
  return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
2170
2170
  },
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 }] };
2171
+ // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2172
+ perspective: ({ value, isArbitrary }) => {
2173
+ if (isArbitrary) {
2174
+ const numPx = parseFloat(value);
2175
+ if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2176
+ return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2179
2177
  }
2180
- return { transform: [{ translateX: v }] };
2178
+ if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2179
+ const n = parseFloat(value);
2180
+ if (isNaN(n)) return null;
2181
+ return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2182
+ }
2183
+ };
2184
+
2185
+ // src/core/resolvers/effects.ts
2186
+ function keyframeDeclToCSS(decl) {
2187
+ return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
2188
+ }
2189
+ function buildKeyframeCSS(name, steps) {
2190
+ const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
2191
+ return `@keyframes ${name} { ${body} }`;
2192
+ }
2193
+ var BUILTIN_ANIMATIONS = {
2194
+ none: { animation: "none" },
2195
+ spin: {
2196
+ animation: "kb-spin 1s linear infinite",
2197
+ __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
2181
2198
  },
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 }] };
2199
+ ping: {
2200
+ animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
2201
+ __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
2202
+ },
2203
+ pulse: {
2204
+ animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
2205
+ __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
2206
+ },
2207
+ bounce: {
2208
+ animation: "kb-bounce 1s infinite",
2209
+ __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) } }"
2210
+ }
2211
+ };
2212
+ function buildAnimationValue(animation, keyframes) {
2213
+ const words = animation.trim().split(/\s+/);
2214
+ const name = words[0];
2215
+ if (!name) return { animation };
2216
+ const builtin = BUILTIN_ANIMATIONS[name];
2217
+ if (builtin?.__keyframe) {
2218
+ const kbName = builtin.animation.split(/\s+/, 1)[0];
2219
+ return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
2220
+ }
2221
+ const steps = keyframes?.[name];
2222
+ return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
2223
+ }
2224
+ var effectResolvers = {
2225
+ // ── Opacity ────────────────────────────────────────────────────────────────
2226
+ opacity: ({ value, isArbitrary }, { opacity }) => {
2227
+ if (isArbitrary) {
2228
+ const v2 = parseFloat(value);
2229
+ return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2189
2230
  }
2190
- return { transform: [{ translateY: v }] };
2231
+ const v = opacity[value];
2232
+ if (v !== void 0) return { opacity: v };
2233
+ const n = parseFloat(value);
2234
+ return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2191
2235
  },
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 };
2236
+ // ── Shadow ─────────────────────────────────────────────────────────────────
2237
+ shadow: ({ value }, { shadow }) => {
2238
+ const key = value === "" ? "DEFAULT" : value;
2239
+ return shadow[key] ?? null;
2240
+ },
2241
+ // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2242
+ // Each variant injects a @keyframes rule once via the __keyframe marker.
2243
+ // resolver.ts detects __keyframe, injects the rule, then strips the marker
2244
+ // so it never reaches element inline styles.
2245
+ animate: ({ value, isArbitrary }, theme) => {
2246
+ if (!getEffectiveIsWeb()) return null;
2247
+ if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2248
+ if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2249
+ const custom = theme.animation?.[value];
2250
+ return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2200
2251
  },
2201
2252
  // ── Transition (web-only; use Animated API on native) ─────────────────────
2202
2253
  transition: ({ value }) => {
@@ -2224,91 +2275,107 @@ var RESOLVERS = {
2224
2275
  if (!isArbitrary && isNaN(parseFloat(value))) return null;
2225
2276
  const ms = isArbitrary ? value : `${value}ms`;
2226
2277
  return { transitionDelay: ms };
2278
+ }
2279
+ };
2280
+
2281
+ // src/core/resolvers/misc.ts
2282
+ var miscResolvers = {
2283
+ // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
2284
+ // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
2285
+ // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
2286
+ // presumably a typo) parses, which is why this only fires when the arbitrary value
2287
+ // itself contains a "property:value" pair; anything else (no colon) still resolves
2288
+ // to null exactly as before this resolver existed, rather than guessing a property.
2289
+ //
2290
+ // Only the FIRST colon splits property from value — the value itself may contain
2291
+ // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
2292
+ // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
2293
+ // custom property) key, so it can be used as the style object key as-written with no
2294
+ // extra case conversion — it only needs to look like a plausible CSS property name.
2295
+ "": ({ value, isArbitrary, negative }) => {
2296
+ if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
2297
+ if (negative) return null;
2298
+ const colonIdx = value.indexOf(":");
2299
+ if (colonIdx <= 0) return null;
2300
+ const prop = value.slice(0, colonIdx).trim();
2301
+ const cssValue = value.slice(colonIdx + 1).trim();
2302
+ if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
2303
+ return { [prop]: cssValue };
2227
2304
  },
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);
2305
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2306
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2307
+ // take stroke/fill as component PROPS, not style entries, so there's no
2308
+ // reliable way to apply a resolved color through the `style` prop there.
2309
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2310
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2311
+ stroke: ({ value, isArbitrary }, { colors }) => {
2312
+ if (!getEffectiveIsWeb()) return null;
2313
+ if (!value) return null;
2314
+ if (value === "none") return { stroke: "none" };
2315
+ if (isArbitrary) {
2316
+ const w = toNativeValue(value);
2317
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2243
2318
  }
2244
- if (value === "inset") {
2245
- return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2319
+ const color = resolveColor(value, colors, false);
2320
+ if (color) return { stroke: color };
2321
+ const n = parseFloat(value);
2322
+ return isNaN(n) ? null : { strokeWidth: n };
2323
+ },
2324
+ fill: ({ value, isArbitrary }, { colors }) => {
2325
+ if (!getEffectiveIsWeb()) return null;
2326
+ if (!value) return null;
2327
+ if (value === "none") return { fill: "none" };
2328
+ if (isArbitrary) return { fill: value };
2329
+ const color = resolveColor(value, colors, false);
2330
+ return color ? { fill: color } : null;
2331
+ },
2332
+ // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2333
+ // Named presets give cross-platform shadow; arbitrary is web-only.
2334
+ "text-shadow": ({ value, isArbitrary }, { colors }) => {
2335
+ if (value === "none") {
2336
+ return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2246
2337
  }
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);
2338
+ const webPresets = {
2339
+ "": "0 2px 4px rgba(0,0,0,0.3)",
2340
+ sm: "0 1px 2px rgba(0,0,0,0.3)",
2341
+ md: "0 4px 6px rgba(0,0,0,0.3)",
2342
+ lg: "0 8px 16px rgba(0,0,0,0.5)",
2343
+ xl: "0 16px 32px rgba(0,0,0,0.5)"
2344
+ };
2345
+ const nativePresets = {
2346
+ "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2347
+ sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2348
+ md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2349
+ lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2350
+ xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2351
+ };
2352
+ if (value in webPresets) {
2353
+ if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2354
+ return nativePresets[value] ?? null;
2251
2355
  }
2252
2356
  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;
2357
+ return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2260
2358
  }
2261
2359
  const color = resolveColor(value, colors, false);
2262
2360
  if (color) {
2263
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
2361
+ return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2264
2362
  }
2265
2363
  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
2364
  }
2305
2365
  };
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
- }
2366
+
2367
+ // src/core/utilities.ts
2368
+ var RESOLVERS = {
2369
+ ...colorResolvers,
2370
+ ...spacingResolvers,
2371
+ ...borderResolvers,
2372
+ ...layoutResolvers,
2373
+ ...typographyResolvers,
2374
+ ...filterResolvers,
2375
+ ...transformResolvers,
2376
+ ...effectResolvers,
2377
+ ...miscResolvers
2378
+ };
2312
2379
  var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2313
2380
  function resolveUtility(parsed, theme) {
2314
2381
  if (!parsed.value) {
@@ -2356,6 +2423,47 @@ function kbachWarn(message) {
2356
2423
  );
2357
2424
  }
2358
2425
 
2426
+ // src/core/cache.ts
2427
+ var LRUCache = class {
2428
+ constructor(capacity = 1e4, onEvict) {
2429
+ this.capacity = capacity;
2430
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2431
+ this.cache = /* @__PURE__ */ new Map();
2432
+ this.onEvict = onEvict;
2433
+ }
2434
+ get(key) {
2435
+ if (!this.cache.has(key)) return void 0;
2436
+ const value = this.cache.get(key);
2437
+ this.cache.delete(key);
2438
+ this.cache.set(key, value);
2439
+ return value;
2440
+ }
2441
+ set(key, value) {
2442
+ if (this.cache.has(key)) {
2443
+ this.cache.delete(key);
2444
+ } else if (this.cache.size >= this.capacity) {
2445
+ const oldestKey = this.cache.keys().next().value;
2446
+ const oldestValue = this.cache.get(oldestKey);
2447
+ this.cache.delete(oldestKey);
2448
+ this.onEvict?.(oldestKey, oldestValue);
2449
+ }
2450
+ this.cache.set(key, value);
2451
+ return this;
2452
+ }
2453
+ has(key) {
2454
+ return this.cache.has(key);
2455
+ }
2456
+ delete(key) {
2457
+ return this.cache.delete(key);
2458
+ }
2459
+ clear() {
2460
+ this.cache.clear();
2461
+ }
2462
+ get size() {
2463
+ return this.cache.size;
2464
+ }
2465
+ };
2466
+
2359
2467
  // src/core/registry.ts
2360
2468
  var BUILTIN_MODIFIERS = {
2361
2469
  // ── Mode ─────────────────────────────────────────────────────────────────
@@ -2442,7 +2550,7 @@ function clearPluginModifiers() {
2442
2550
  _invalidate();
2443
2551
  }
2444
2552
  var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
2445
- var _namedModifierCache = /* @__PURE__ */ new Map();
2553
+ var _namedModifierCache = new LRUCache(1e4);
2446
2554
  function getNamedGroupPeerModifier(name) {
2447
2555
  const cached = _namedModifierCache.get(name);
2448
2556
  if (cached) return cached;
@@ -2613,51 +2721,10 @@ function findOuterColon(s) {
2613
2721
  return -1;
2614
2722
  }
2615
2723
 
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
2724
  // src/core/resolver.ts
2658
- var FONT_KEY = "__kbach_default_font__";
2725
+ var _defaultFontFamily;
2659
2726
  function setDefaultFontFamily(font) {
2660
- globalThis[FONT_KEY] = font;
2727
+ _defaultFontFamily = font;
2661
2728
  }
2662
2729
  var _styleEl = null;
2663
2730
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
@@ -2677,9 +2744,9 @@ function evictInjectedRule(rule) {
2677
2744
  }
2678
2745
  }
2679
2746
  var _injectedRules = new LRUCache(5e4, evictInjectedRule);
2680
- var _CSS_DISABLED_KEY = "__kbach_runtime_css_disabled__";
2747
+ var _runtimeCSSDisabled = false;
2681
2748
  function isRuntimeCSSDisabled() {
2682
- return !!globalThis[_CSS_DISABLED_KEY];
2749
+ return _runtimeCSSDisabled;
2683
2750
  }
2684
2751
  function getStyleEl() {
2685
2752
  if (_styleEl) return _styleEl;
@@ -2902,14 +2969,7 @@ function deepMerge(base, override) {
2902
2969
  }
2903
2970
  return result;
2904
2971
  }
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
- }
2972
+ var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
2913
2973
  function resolveOneRef(ref, colors) {
2914
2974
  const lastDash = ref.lastIndexOf("-");
2915
2975
  if (lastDash <= 0) return null;
@@ -2983,8 +3043,7 @@ function buildConfig(userConfig) {
2983
3043
  clearPluginUtilities();
2984
3044
  clearPluginModifiers();
2985
3045
  }
2986
- const _store = getConfigStore();
2987
- for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
3046
+ for (const k of Object.keys(configStore.customVariants)) delete configStore.customVariants[k];
2988
3047
  const pluginAPI = makePluginAPI(resolved.theme);
2989
3048
  for (const plugin of resolved.plugins) {
2990
3049
  plugin(pluginAPI);
@@ -3031,26 +3090,7 @@ function makePluginAPI(theme) {
3031
3090
  }
3032
3091
  };
3033
3092
  }
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
- );
3093
+ var customVariants = configStore.customVariants;
3054
3094
 
3055
3095
  // src/vite-plugin.ts
3056
3096
  var process2 = processImport;
@@ -3410,7 +3450,9 @@ function pushTokens(str, into) {
3410
3450
  if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
3411
3451
  }
3412
3452
  }
3413
- function scanDir(dir, onFile) {
3453
+ var MAX_SCAN_DEPTH = 10;
3454
+ function scanDir(dir, onFile, depth = 0) {
3455
+ if (depth > MAX_SCAN_DEPTH) return;
3414
3456
  let entries;
3415
3457
  try {
3416
3458
  entries = readdirSync(dir);
@@ -3425,7 +3467,7 @@ function scanDir(dir, onFile) {
3425
3467
  const full = join(dir, entry);
3426
3468
  try {
3427
3469
  const st = statSync(full);
3428
- if (st.isDirectory()) scanDir(full, onFile);
3470
+ if (st.isDirectory()) scanDir(full, onFile, depth + 1);
3429
3471
  else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
3430
3472
  } catch (err) {
3431
3473
  warn(`Can't process ${full}: ${err.message}`);
@@ -3443,7 +3485,8 @@ function scanCssFileInto(filePath, into) {
3443
3485
  } catch {
3444
3486
  }
3445
3487
  }
3446
- function scanCssSelectorsInto(dir, into) {
3488
+ function scanCssSelectorsInto(dir, into, depth = 0) {
3489
+ if (depth > MAX_SCAN_DEPTH) return;
3447
3490
  let entries;
3448
3491
  try {
3449
3492
  entries = readdirSync(dir);
@@ -3455,7 +3498,7 @@ function scanCssSelectorsInto(dir, into) {
3455
3498
  const full = join(dir, entry);
3456
3499
  try {
3457
3500
  const st = statSync(full);
3458
- if (st.isDirectory()) scanCssSelectorsInto(full, into);
3501
+ if (st.isDirectory()) scanCssSelectorsInto(full, into, depth + 1);
3459
3502
  else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
3460
3503
  } catch {
3461
3504
  }
@@ -3522,6 +3565,17 @@ function kbach(userConfigOrOptions) {
3522
3565
  function generateCSS() {
3523
3566
  return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
3524
3567
  }
3568
+ function syncMainCSSFile(server) {
3569
+ const active = /* @__PURE__ */ new Set();
3570
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3571
+ for (const tok of tokenCSS.keys()) {
3572
+ if (!active.has(tok)) tokenCSS.delete(tok);
3573
+ }
3574
+ if (mainCSSFile) {
3575
+ const changed = writeKbachToFile(mainCSSFile, generateCSS());
3576
+ if (changed) server.watcher.emit("change", mainCSSFile);
3577
+ }
3578
+ }
3525
3579
  return {
3526
3580
  name: "kbach",
3527
3581
  enforce: "pre",
@@ -3581,6 +3635,37 @@ ${code}`,
3581
3635
  initialScan();
3582
3636
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3583
3637
  },
3638
+ // Vite's `handleHotUpdate` hook only fires for `type === "update"` (plain
3639
+ // file edits) — it's never called for file create/delete, which land on
3640
+ // the watcher's own 'add'/'unlink' events instead (confirmed against
3641
+ // vite's own onHMRUpdate/handleHMRUpdate: the hook loop is gated on
3642
+ // `type === "update"`). Without also listening on those two events here,
3643
+ // creating a new file with new Kbach classes never adds them to
3644
+ // kbach.css, and deleting a file never removes its classes, until a full
3645
+ // dev-server restart. configureServer gives access to the raw watcher so
3646
+ // both cases can share the same sync logic as a plain edit.
3647
+ configureServer(server) {
3648
+ const onAddOrUnlink = (file, isUnlink) => {
3649
+ if (file.includes("node_modules")) return;
3650
+ if (CSS_FILE_RE.test(file)) {
3651
+ if (!isUnlink) scanCssFileInto(file, projectCssClasses);
3652
+ return;
3653
+ }
3654
+ if (!/\.(tsx?|jsx?)$/.test(file)) return;
3655
+ if (isUnlink) {
3656
+ fileTokens.delete(normPath(file));
3657
+ } else {
3658
+ try {
3659
+ processFile(file, readFileSync(file, "utf-8"));
3660
+ } catch {
3661
+ fileTokens.delete(normPath(file));
3662
+ }
3663
+ }
3664
+ syncMainCSSFile(server);
3665
+ };
3666
+ server.watcher.on("add", (file) => onAddOrUnlink(file, false));
3667
+ server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
3668
+ },
3584
3669
  handleHotUpdate({ file, server }) {
3585
3670
  if (file.includes("node_modules")) return;
3586
3671
  if (CSS_FILE_RE.test(file)) {
@@ -3593,15 +3678,7 @@ ${code}`,
3593
3678
  } catch {
3594
3679
  fileTokens.delete(normPath(file));
3595
3680
  }
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
- }
3681
+ syncMainCSSFile(server);
3605
3682
  }
3606
3683
  };
3607
3684
  }