@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.
@@ -649,14 +649,16 @@ var defaultTheme = {
649
649
  animation: {}
650
650
  };
651
651
 
652
- // src/core/utilities.ts
652
+ // src/core/resolvers/color.ts
653
653
  function resolveColor(value, colors, isArbitrary) {
654
654
  if (isArbitrary) return value;
655
655
  const slashIdx = value.indexOf("/");
656
656
  const colorPart = slashIdx > 0 ? value.slice(0, slashIdx) : value;
657
657
  const opacityPart = slashIdx > 0 ? value.slice(slashIdx + 1) : null;
658
658
  let hex = null;
659
- if (colorPart in colors) {
659
+ if (colorPart.startsWith("[") && colorPart.endsWith("]")) {
660
+ hex = colorPart.slice(1, -1);
661
+ } else if (colorPart in colors) {
660
662
  const entry = colors[colorPart];
661
663
  if (typeof entry === "string") hex = entry;
662
664
  else if (typeof entry === "object" && "6" in entry) hex = entry["6"];
@@ -721,6 +723,86 @@ function withOpacityVar(color, varName) {
721
723
  const [r, g, b] = rgb;
722
724
  return `rgba(${r},${g},${b},var(${varName},1))`;
723
725
  }
726
+ var colorResolvers = {
727
+ // ── Background ─────────────────────────────────────────────────────────────
728
+ bg: ({ value, isArbitrary }, { colors }) => {
729
+ const color = resolveColor(value, colors, isArbitrary);
730
+ if (!color) return null;
731
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
732
+ },
733
+ "bg-opacity": ({ value, isArbitrary }, _) => {
734
+ if (!getEffectiveIsWeb()) return null;
735
+ const n = parseFloat(value);
736
+ if (isNaN(n)) return null;
737
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
738
+ return { "--bg-opacity": v };
739
+ },
740
+ // ── Background gradient (CSS variable gradient stops, web-only) ──────────
741
+ "bg-gradient-to": ({ value }) => {
742
+ if (!getEffectiveIsWeb()) return null;
743
+ const directions = {
744
+ t: "to top",
745
+ tr: "to top right",
746
+ r: "to right",
747
+ br: "to bottom right",
748
+ b: "to bottom",
749
+ bl: "to bottom left",
750
+ l: "to left",
751
+ tl: "to top left"
752
+ };
753
+ const dir = directions[value];
754
+ if (!dir) return null;
755
+ return {
756
+ backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
757
+ };
758
+ },
759
+ from: ({ value, isArbitrary }, { colors }) => {
760
+ if (!getEffectiveIsWeb()) return null;
761
+ const color = resolveColor(value, colors, isArbitrary);
762
+ if (!color) return null;
763
+ return {
764
+ "--kb-gradient-from": color,
765
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
766
+ };
767
+ },
768
+ via: ({ value, isArbitrary }, { colors }) => {
769
+ if (!getEffectiveIsWeb()) return null;
770
+ const color = resolveColor(value, colors, isArbitrary);
771
+ if (!color) return null;
772
+ return {
773
+ "--kb-gradient-via": color,
774
+ "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
775
+ };
776
+ },
777
+ to: ({ value, isArbitrary }, { colors }) => {
778
+ if (!getEffectiveIsWeb()) return null;
779
+ const color = resolveColor(value, colors, isArbitrary);
780
+ if (!color) return null;
781
+ return { "--kb-gradient-to": color };
782
+ },
783
+ // ── Tint color (native-only, for Image and icon components) ──────────────
784
+ tint: ({ value, isArbitrary }, { colors }) => {
785
+ if (getEffectiveIsWeb()) return null;
786
+ const color = resolveColor(value, colors, isArbitrary);
787
+ return color ? { tintColor: color } : null;
788
+ },
789
+ // ── Caret color (web-only) ────────────────────────────────────────────────
790
+ caret: ({ value, isArbitrary }, { colors }) => {
791
+ if (!getEffectiveIsWeb()) return null;
792
+ if (value === "auto" || value === "transparent") return { caretColor: value };
793
+ const color = resolveColor(value, colors, isArbitrary);
794
+ return color ? { caretColor: color } : null;
795
+ },
796
+ // ── Accent color (web-only) ───────────────────────────────────────────────
797
+ accent: ({ value, isArbitrary }, { colors }) => {
798
+ if (!getEffectiveIsWeb()) return null;
799
+ if (value === "auto") return { accentColor: "auto" };
800
+ const color = resolveColor(value, colors, isArbitrary);
801
+ return color ? { accentColor: color } : null;
802
+ }
803
+ };
804
+
805
+ // src/core/resolvers/spacing.ts
724
806
  function resolveSpacing(value, negative, spacing, isArbitrary) {
725
807
  if (isArbitrary) {
726
808
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -753,516 +835,174 @@ function resolveSizing(value, spacing, isArbitrary) {
753
835
  }
754
836
  return null;
755
837
  }
838
+ function makeSpacingResolver(prop) {
839
+ return ({ value, negative, isArbitrary }, { spacing }) => {
840
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
841
+ return v !== null ? { [prop]: v } : null;
842
+ };
843
+ }
844
+ var PADDING_MARGIN_PROPS = {
845
+ p: "padding",
846
+ px: "paddingHorizontal",
847
+ py: "paddingVertical",
848
+ pt: "paddingTop",
849
+ pr: "paddingRight",
850
+ pb: "paddingBottom",
851
+ pl: "paddingLeft",
852
+ m: "margin",
853
+ mx: "marginHorizontal",
854
+ my: "marginVertical",
855
+ mt: "marginTop",
856
+ mr: "marginRight",
857
+ mb: "marginBottom",
858
+ ml: "marginLeft"
859
+ };
860
+ var spacingResolvers = {
861
+ // ── Sizing ─────────────────────────────────────────────────────────────────
862
+ w: ({ value, isArbitrary }, { spacing }) => {
863
+ const v = resolveSizing(value, spacing, isArbitrary);
864
+ return v !== null ? { width: v } : null;
865
+ },
866
+ h: ({ value, isArbitrary }, { spacing }) => {
867
+ const v = resolveSizing(value, spacing, isArbitrary);
868
+ return v !== null ? { height: v } : null;
869
+ },
870
+ "min-w": ({ value, isArbitrary }, { spacing }) => {
871
+ const v = resolveSizing(value, spacing, isArbitrary);
872
+ return v !== null ? { minWidth: v } : null;
873
+ },
874
+ "min-h": ({ value, isArbitrary }, { spacing }) => {
875
+ const v = resolveSizing(value, spacing, isArbitrary);
876
+ return v !== null ? { minHeight: v } : null;
877
+ },
878
+ "max-w": ({ value, isArbitrary }, { spacing }) => {
879
+ const v = resolveSizing(value, spacing, isArbitrary);
880
+ return v !== null ? { maxWidth: v } : null;
881
+ },
882
+ "max-h": ({ value, isArbitrary }, { spacing }) => {
883
+ const v = resolveSizing(value, spacing, isArbitrary);
884
+ return v !== null ? { maxHeight: v } : null;
885
+ },
886
+ // ── Size shorthand (sets width AND height in one utility) ────────────────
887
+ size: ({ value, isArbitrary }, { spacing }) => {
888
+ const v = resolveSizing(value, spacing, isArbitrary);
889
+ return v !== null ? { width: v, height: v } : null;
890
+ },
891
+ // ── Flex basis ─────────────────────────────────────────────────────────────
892
+ basis: ({ value, isArbitrary }, { spacing }) => {
893
+ const v = resolveSizing(value, spacing, isArbitrary);
894
+ return v !== null ? { flexBasis: v } : null;
895
+ },
896
+ // ── Gap ────────────────────────────────────────────────────────────────────
897
+ gap: ({ value, negative, isArbitrary }, { spacing }) => {
898
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
899
+ return v !== null ? { gap: v } : null;
900
+ },
901
+ "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
902
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
903
+ return v !== null ? { columnGap: v } : null;
904
+ },
905
+ "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
906
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
907
+ return v !== null ? { rowGap: v } : null;
908
+ },
909
+ // ── Position ───────────────────────────────────────────────────────────────
910
+ top: ({ value, negative, isArbitrary }, { spacing }) => {
911
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
912
+ return v !== null ? { top: v } : null;
913
+ },
914
+ right: ({ value, negative, isArbitrary }, { spacing }) => {
915
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
916
+ return v !== null ? { right: v } : null;
917
+ },
918
+ bottom: ({ value, negative, isArbitrary }, { spacing }) => {
919
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
920
+ return v !== null ? { bottom: v } : null;
921
+ },
922
+ left: ({ value, negative, isArbitrary }, { spacing }) => {
923
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
924
+ return v !== null ? { left: v } : null;
925
+ },
926
+ inset: ({ value, negative, isArbitrary }, { spacing }) => {
927
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
928
+ return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
929
+ },
930
+ "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
931
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
932
+ return v !== null ? { left: v, right: v } : null;
933
+ },
934
+ "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
935
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
936
+ return v !== null ? { top: v, bottom: v } : null;
937
+ },
938
+ // ── Translate ──────────────────────────────────────────────────────────────
939
+ "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
940
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
941
+ if (v === null) return null;
942
+ if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
943
+ if (typeof v === "string") {
944
+ const n = parseFloat(v);
945
+ return isNaN(n) ? null : { transform: [{ translateX: n }] };
946
+ }
947
+ return { transform: [{ translateX: v }] };
948
+ },
949
+ "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
950
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
951
+ if (v === null) return null;
952
+ if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
953
+ if (typeof v === "string") {
954
+ const n = parseFloat(v);
955
+ return isNaN(n) ? null : { transform: [{ translateY: n }] };
956
+ }
957
+ return { transform: [{ translateY: v }] };
958
+ },
959
+ // ── Space between ─────────────────────────────────────────────────────────
960
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
961
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
962
+ "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
963
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
964
+ if (v === null) return null;
965
+ if (getEffectiveIsWeb()) return { __spaceX: v };
966
+ return { columnGap: v };
967
+ },
968
+ "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
969
+ const v = resolveSpacing(value, negative, spacing, isArbitrary);
970
+ if (v === null) return null;
971
+ if (getEffectiveIsWeb()) return { __spaceY: v };
972
+ return { rowGap: v };
973
+ }
974
+ };
975
+ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
976
+ spacingResolvers[utility] = makeSpacingResolver(prop);
977
+ }
978
+
979
+ // src/core/resolvers/border.ts
756
980
  function resolveRadius(value, radii, isArbitrary) {
757
981
  if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
758
982
  const key = value === "" ? "DEFAULT" : value;
759
983
  return radii[key] ?? null;
760
984
  }
761
- function resolveFontSize(value, fontSizes, isArbitrary) {
762
- if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
763
- return fontSizes[value] ?? null;
985
+ function makeBorderSideResolver(widthProp, colorProp) {
986
+ return ({ value, isArbitrary }, { colors, borderWidth }) => {
987
+ if (!value) return { [widthProp]: 1 };
988
+ if (isArbitrary) {
989
+ const w2 = toNativeValue(value);
990
+ if (typeof w2 === "number") return { [widthProp]: w2 };
991
+ return { [colorProp]: value };
992
+ }
993
+ const color = resolveColor(value, colors, false);
994
+ if (color) return { [colorProp]: color };
995
+ const w = borderWidth[value];
996
+ return w !== void 0 ? { [widthProp]: w } : null;
997
+ };
764
998
  }
765
- 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,)";
766
- 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,)";
767
- var _standalone = null;
768
- function buildStandalone() {
769
- const web = getEffectiveIsWeb();
770
- return {
771
- // Display
772
- // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
773
- // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
774
- // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
775
- // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
776
- // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
777
- // time, but without it these utilities couldn't re-show a `hidden` element on
778
- // native either (null → no style applied → still display:'none').
779
- flex: { display: "flex" },
780
- block: web ? { display: "block" } : null,
781
- "inline-block": { display: web ? "inline-block" : "flex" },
782
- inline: { display: web ? "inline" : "flex" },
783
- grid: web ? { display: "grid" } : null,
784
- grd: web ? { display: "grid" } : null,
785
- "inline-flex": { display: web ? "inline-flex" : "flex" },
786
- "inline-grid": { display: web ? "inline-grid" : "flex" },
787
- hidden: { display: "none" },
788
- contents: web ? { display: "contents" } : null,
789
- "flow-root": web ? { display: "flow-root" } : null,
790
- // Flex direction (both web and native)
791
- "flex-row": { flexDirection: "row" },
792
- "flex-col": { flexDirection: "column" },
793
- "flex-row-reverse": { flexDirection: "row-reverse" },
794
- "flex-col-reverse": { flexDirection: "column-reverse" },
795
- // Flex wrap (both)
796
- "flex-wrap": { flexWrap: "wrap" },
797
- "flex-wrap-reverse": { flexWrap: "wrap-reverse" },
798
- "flex-nowrap": { flexWrap: "nowrap" },
799
- // Flex grow / shrink (both)
800
- "flex-grow": { flexGrow: 1 },
801
- "flex-grow-0": { flexGrow: 0 },
802
- "flex-shrink": { flexShrink: 1 },
803
- "flex-shrink-0": { flexShrink: 0 },
804
- // Align items (both)
805
- "items-start": { alignItems: "flex-start" },
806
- "items-end": { alignItems: "flex-end" },
807
- "items-center": { alignItems: "center" },
808
- "items-baseline": { alignItems: "baseline" },
809
- "items-stretch": { alignItems: "stretch" },
810
- // Justify content (both)
811
- "justify-start": { justifyContent: "flex-start" },
812
- "justify-end": { justifyContent: "flex-end" },
813
- "justify-center": { justifyContent: "center" },
814
- "justify-between": { justifyContent: "space-between" },
815
- "justify-around": { justifyContent: "space-around" },
816
- "justify-evenly": { justifyContent: "space-evenly" },
817
- // Align content (both)
818
- "content-start": { alignContent: "flex-start" },
819
- "content-end": { alignContent: "flex-end" },
820
- "content-center": { alignContent: "center" },
821
- "content-between": { alignContent: "space-between" },
822
- "content-around": { alignContent: "space-around" },
823
- "content-evenly": { alignContent: "space-evenly" },
824
- "content-stretch": { alignContent: "stretch" },
825
- // Align self (both)
826
- "self-auto": { alignSelf: "auto" },
827
- "self-start": { alignSelf: "flex-start" },
828
- "self-end": { alignSelf: "flex-end" },
829
- "self-center": { alignSelf: "center" },
830
- "self-stretch": { alignSelf: "stretch" },
831
- "self-baseline": { alignSelf: "baseline" },
832
- // Text align (both)
833
- "text-left": { textAlign: "left" },
834
- "text-right": { textAlign: "right" },
835
- "text-center": { textAlign: "center" },
836
- "text-justify": { textAlign: "justify" },
837
- // Font weight shortcuts (both)
838
- "font-thin": { fontWeight: "100" },
839
- "font-extralight": { fontWeight: "200" },
840
- "font-light": { fontWeight: "300" },
841
- "font-normal": { fontWeight: "400" },
842
- "font-medium": { fontWeight: "500" },
843
- "font-semibold": { fontWeight: "600" },
844
- "font-bold": { fontWeight: "700" },
845
- "font-extrabold": { fontWeight: "800" },
846
- "font-black": { fontWeight: "900" },
847
- // Text decoration (overline is web-only)
848
- underline: { textDecorationLine: "underline" },
849
- overline: web ? { textDecorationLine: "overline" } : null,
850
- "line-through": { textDecorationLine: "line-through" },
851
- "no-underline": { textDecorationLine: "none" },
852
- // Text transform (both)
853
- uppercase: { textTransform: "uppercase" },
854
- lowercase: { textTransform: "lowercase" },
855
- capitalize: { textTransform: "capitalize" },
856
- "normal-case": { textTransform: "none" },
857
- // Font style (both)
858
- italic: { fontStyle: "italic" },
859
- "non-italic": { fontStyle: "normal" },
860
- // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
861
- relative: { position: "relative" },
862
- absolute: { position: "absolute" },
863
- fixed: web ? { position: "fixed" } : null,
864
- sticky: web ? { position: "sticky" } : null,
865
- static: web ? { position: "static" } : null,
866
- // Visibility (no `visibility` prop on native; use opacity instead)
867
- visible: web ? { visibility: "visible" } : null,
868
- invisible: web ? { visibility: "hidden" } : { opacity: 0 },
869
- // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
870
- "overflow-hidden": { overflow: "hidden" },
871
- "overflow-visible": { overflow: "visible" },
872
- "overflow-scroll": { overflow: "scroll" },
873
- "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
874
- // overflowX / overflowY are web-only
875
- "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
876
- "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
877
- "overflow-x-auto": web ? { overflowX: "auto" } : null,
878
- "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
879
- "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
880
- "overflow-y-auto": web ? { overflowY: "auto" } : null,
881
- // Object fit (web-only; use resizeMode prop on RN Image)
882
- "object-contain": web ? { objectFit: "contain" } : null,
883
- "object-cover": web ? { objectFit: "cover" } : null,
884
- "object-fill": web ? { objectFit: "fill" } : null,
885
- "object-none": web ? { objectFit: "none" } : null,
886
- "object-scale-down": web ? { objectFit: "scale-down" } : null,
887
- // Border style (both)
888
- "border-solid": { borderStyle: "solid" },
889
- "border-dashed": { borderStyle: "dashed" },
890
- "border-dotted": { borderStyle: "dotted" },
891
- "border-none": { borderWidth: 0 },
892
- // Border sides default width (both)
893
- "border-t": { borderTopWidth: 1 },
894
- "border-r": { borderRightWidth: 1 },
895
- "border-b": { borderBottomWidth: 1 },
896
- "border-l": { borderLeftWidth: 1 },
897
- // Cursor (web-only)
898
- "cursor-auto": web ? { cursor: "auto" } : null,
899
- "cursor-default": web ? { cursor: "default" } : null,
900
- "cursor-pointer": web ? { cursor: "pointer" } : null,
901
- "cursor-wait": web ? { cursor: "wait" } : null,
902
- "cursor-text": web ? { cursor: "text" } : null,
903
- "cursor-move": web ? { cursor: "move" } : null,
904
- "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
905
- // User select (web-only)
906
- "select-none": web ? { userSelect: "none" } : null,
907
- "select-text": web ? { userSelect: "text" } : null,
908
- "select-all": web ? { userSelect: "all" } : null,
909
- "select-auto": web ? { userSelect: "auto" } : null,
910
- // Pointer events (both)
911
- "pointer-events-none": { pointerEvents: "none" },
912
- "pointer-events-auto": { pointerEvents: "auto" },
913
- // Whitespace (web-only; RN Text uses numberOfLines prop instead)
914
- "whitespace-normal": web ? { whiteSpace: "normal" } : null,
915
- "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
916
- "whitespace-pre": web ? { whiteSpace: "pre" } : null,
917
- "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
918
- "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
919
- // Word break (web-only)
920
- "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
921
- "break-words": web ? { overflowWrap: "break-word" } : null,
922
- "break-all": web ? { wordBreak: "break-all" } : null,
923
- // Misc web-only
924
- truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
925
- "box-border": web ? { boxSizing: "border-box" } : null,
926
- "box-content": web ? { boxSizing: "content-box" } : null,
927
- "appearance-none": web ? { appearance: "none" } : null,
928
- "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
929
- outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
930
- resize: web ? { resize: "both" } : null,
931
- "resize-none": web ? { resize: "none" } : null,
932
- "resize-y": web ? { resize: "vertical" } : null,
933
- "resize-x": web ? { resize: "horizontal" } : null,
934
- antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
935
- "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
936
- "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
937
- "sr-only": web ? {
938
- position: "absolute",
939
- width: 1,
940
- height: 1,
941
- padding: 0,
942
- margin: -1,
943
- overflow: "hidden",
944
- clip: "rect(0,0,0,0)",
945
- whiteSpace: "nowrap",
946
- border: 0
947
- } : null,
948
- "not-sr-only": web ? {
949
- position: "static",
950
- width: "auto",
951
- height: "auto",
952
- padding: 0,
953
- margin: 0,
954
- overflow: "visible",
955
- clip: "auto",
956
- whiteSpace: "normal"
957
- } : null,
958
- // Lists (web-only)
959
- "list-none": web ? { listStyleType: "none" } : null,
960
- "list-disc": web ? { listStyleType: "disc" } : null,
961
- "list-decimal": web ? { listStyleType: "decimal" } : null,
962
- // Background clip (web-only)
963
- "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
964
- "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
965
- "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
966
- "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
967
- // Background image
968
- "bg-none": web ? { backgroundImage: "none" } : null,
969
- // Background size (web-only)
970
- "bg-auto": web ? { backgroundSize: "auto" } : null,
971
- "bg-cover": web ? { backgroundSize: "cover" } : null,
972
- "bg-contain": web ? { backgroundSize: "contain" } : null,
973
- // Background position (web-only)
974
- "bg-center": web ? { backgroundPosition: "center" } : null,
975
- "bg-top": web ? { backgroundPosition: "top" } : null,
976
- "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
977
- "bg-left": web ? { backgroundPosition: "left" } : null,
978
- "bg-right": web ? { backgroundPosition: "right" } : null,
979
- "bg-left-top": web ? { backgroundPosition: "left top" } : null,
980
- "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
981
- "bg-right-top": web ? { backgroundPosition: "right top" } : null,
982
- "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
983
- // Background repeat (web-only)
984
- "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
985
- "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
986
- "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
987
- "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
988
- "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
989
- "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
990
- // Background attachment (web-only)
991
- "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
992
- "bg-local": web ? { backgroundAttachment: "local" } : null,
993
- "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
994
- // Object position (web-only)
995
- "object-center": web ? { objectPosition: "center" } : null,
996
- "object-top": web ? { objectPosition: "top" } : null,
997
- "object-bottom": web ? { objectPosition: "bottom" } : null,
998
- "object-left": web ? { objectPosition: "left" } : null,
999
- "object-right": web ? { objectPosition: "right" } : null,
1000
- "object-left-top": web ? { objectPosition: "left top" } : null,
1001
- "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1002
- "object-right-top": web ? { objectPosition: "right top" } : null,
1003
- "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1004
- // Table (web-only)
1005
- table: web ? { display: "table" } : null,
1006
- "table-auto": web ? { tableLayout: "auto" } : null,
1007
- "table-fixed": web ? { tableLayout: "fixed" } : null,
1008
- "caption-top": web ? { captionSide: "top" } : null,
1009
- "caption-bottom": web ? { captionSide: "bottom" } : null,
1010
- "border-collapse": web ? { borderCollapse: "collapse" } : null,
1011
- "border-separate": web ? { borderCollapse: "separate" } : null,
1012
- // List style position (web-only)
1013
- "list-inside": web ? { listStylePosition: "inside" } : null,
1014
- "list-outside": web ? { listStylePosition: "outside" } : null,
1015
- // Font variant numeric (web-only)
1016
- "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1017
- ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1018
- "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1019
- "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1020
- "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1021
- "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1022
- "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1023
- "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1024
- "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1025
- // Isolation (web-only)
1026
- isolate: web ? { isolation: "isolate" } : null,
1027
- "isolation-auto": web ? { isolation: "auto" } : null,
1028
- // Backface visibility (both iOS and Android)
1029
- "backface-visible": { backfaceVisibility: "visible" },
1030
- "backface-hidden": { backfaceVisibility: "hidden" },
1031
- // CSS filters — standalone = default/full effect (web-only)
1032
- grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1033
- invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1034
- sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1035
- // Divide none
1036
- "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1037
- // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1038
- group: web ? {} : null,
1039
- peer: web ? {} : null,
1040
- // Text-wrap (web-only)
1041
- "text-wrap": web ? { textWrap: "wrap" } : null,
1042
- "text-nowrap": web ? { textWrap: "nowrap" } : null,
1043
- "text-balance": web ? { textWrap: "balance" } : null,
1044
- "text-pretty": web ? { textWrap: "pretty" } : null,
1045
- // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1046
- // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1047
- // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1048
- // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1049
- // is unchanged since there's no dynamic chrome to account for.
1050
- // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1051
- "w-screen": web ? { width: "100dvw" } : null,
1052
- "min-h-screen": { minHeight: "100dvh" },
1053
- "max-h-screen": { maxHeight: "100dvh" },
1054
- "min-w-screen": web ? { minWidth: "100dvw" } : null,
1055
- "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1056
- // max-w named container sizes (mirrors Tailwind's container scale)
1057
- "max-w-none": { maxWidth: "none" },
1058
- "max-w-xs": { maxWidth: 320 },
1059
- "max-w-sm": { maxWidth: 384 },
1060
- "max-w-md": { maxWidth: 448 },
1061
- "max-w-lg": { maxWidth: 512 },
1062
- "max-w-xl": { maxWidth: 576 },
1063
- "max-w-2xl": { maxWidth: 672 },
1064
- "max-w-3xl": { maxWidth: 768 },
1065
- "max-w-4xl": { maxWidth: 896 },
1066
- "max-w-5xl": { maxWidth: 1024 },
1067
- "max-w-6xl": { maxWidth: 1152 },
1068
- "max-w-7xl": { maxWidth: 1280 },
1069
- "max-w-prose": web ? { maxWidth: "65ch" } : null,
1070
- // Extended cursors (web-only)
1071
- "cursor-grab": web ? { cursor: "grab" } : null,
1072
- "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1073
- "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1074
- "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1075
- "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1076
- "cursor-help": web ? { cursor: "help" } : null,
1077
- "cursor-none": web ? { cursor: "none" } : null,
1078
- // Overflow clip (web-only)
1079
- "overflow-clip": web ? { overflow: "clip" } : null,
1080
- "overflow-x-clip": web ? { overflowX: "clip" } : null,
1081
- "overflow-y-clip": web ? { overflowY: "clip" } : null,
1082
- // Scroll behavior (web-only)
1083
- "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1084
- "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1085
- // Float (web-only)
1086
- "float-left": web ? { float: "left" } : null,
1087
- "float-right": web ? { float: "right" } : null,
1088
- "float-start": web ? { float: "inline-start" } : null,
1089
- "float-end": web ? { float: "inline-end" } : null,
1090
- "float-none": web ? { float: "none" } : null,
1091
- // Clear (web-only)
1092
- "clear-left": web ? { clear: "left" } : null,
1093
- "clear-right": web ? { clear: "right" } : null,
1094
- "clear-both": web ? { clear: "both" } : null,
1095
- "clear-start": web ? { clear: "inline-start" } : null,
1096
- "clear-end": web ? { clear: "inline-end" } : null,
1097
- "clear-none": web ? { clear: "none" } : null,
1098
- // Vertical align (web-only)
1099
- "align-baseline": web ? { verticalAlign: "baseline" } : null,
1100
- "align-top": web ? { verticalAlign: "top" } : null,
1101
- "align-middle": web ? { verticalAlign: "middle" } : null,
1102
- "align-bottom": web ? { verticalAlign: "bottom" } : null,
1103
- "align-text-top": web ? { verticalAlign: "text-top" } : null,
1104
- "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1105
- "align-sub": web ? { verticalAlign: "sub" } : null,
1106
- "align-super": web ? { verticalAlign: "super" } : null,
1107
- // Touch action (web-only)
1108
- "touch-auto": web ? { touchAction: "auto" } : null,
1109
- "touch-none": web ? { touchAction: "none" } : null,
1110
- "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1111
- "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1112
- "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1113
- "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1114
- "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1115
- "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1116
- "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1117
- "touch-manipulation": web ? { touchAction: "manipulation" } : null
1118
- };
1119
- }
1120
- function getStandalone() {
1121
- if (!_standalone) _standalone = buildStandalone();
1122
- return _standalone;
1123
- }
1124
- function makeSpacingResolver(prop) {
1125
- return ({ value, negative, isArbitrary }, { spacing }) => {
1126
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
1127
- return v !== null ? { [prop]: v } : null;
1128
- };
1129
- }
1130
- var PADDING_MARGIN_PROPS = {
1131
- p: "padding",
1132
- px: "paddingHorizontal",
1133
- py: "paddingVertical",
1134
- pt: "paddingTop",
1135
- pr: "paddingRight",
1136
- pb: "paddingBottom",
1137
- pl: "paddingLeft",
1138
- m: "margin",
1139
- mx: "marginHorizontal",
1140
- my: "marginVertical",
1141
- mt: "marginTop",
1142
- mr: "marginRight",
1143
- mb: "marginBottom",
1144
- ml: "marginLeft"
1145
- };
1146
- function makeBorderSideResolver(widthProp, colorProp) {
1147
- return ({ value, isArbitrary }, { colors, borderWidth }) => {
1148
- if (!value) return { [widthProp]: 1 };
1149
- if (isArbitrary) {
1150
- const w2 = toNativeValue(value);
1151
- if (typeof w2 === "number") return { [widthProp]: w2 };
1152
- return { [colorProp]: value };
1153
- }
1154
- const color = resolveColor(value, colors, false);
1155
- if (color) return { [colorProp]: color };
1156
- const w = borderWidth[value];
1157
- return w !== void 0 ? { [widthProp]: w } : null;
1158
- };
1159
- }
1160
- var BORDER_SIDE_PROPS = {
1161
- "border-t": ["borderTopWidth", "borderTopColor"],
1162
- "border-r": ["borderRightWidth", "borderRightColor"],
1163
- "border-b": ["borderBottomWidth", "borderBottomColor"],
1164
- "border-l": ["borderLeftWidth", "borderLeftColor"]
1165
- };
1166
- function keyframeDeclToCSS(decl) {
1167
- return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
1168
- }
1169
- function buildKeyframeCSS(name, steps) {
1170
- const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
1171
- return `@keyframes ${name} { ${body} }`;
1172
- }
1173
- var BUILTIN_ANIMATIONS = {
1174
- none: { animation: "none" },
1175
- spin: {
1176
- animation: "kb-spin 1s linear infinite",
1177
- __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
1178
- },
1179
- ping: {
1180
- animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
1181
- __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
1182
- },
1183
- pulse: {
1184
- animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
1185
- __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
1186
- },
1187
- bounce: {
1188
- animation: "kb-bounce 1s infinite",
1189
- __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) } }"
1190
- }
1191
- };
1192
- function buildAnimationValue(animation, keyframes) {
1193
- const words = animation.trim().split(/\s+/);
1194
- const name = words[0];
1195
- if (!name) return { animation };
1196
- const builtin = BUILTIN_ANIMATIONS[name];
1197
- if (builtin?.__keyframe) {
1198
- const kbName = builtin.animation.split(/\s+/, 1)[0];
1199
- return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
1200
- }
1201
- const steps = keyframes?.[name];
1202
- return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
1203
- }
1204
- var RESOLVERS = {
1205
- // ── Background ─────────────────────────────────────────────────────────────
1206
- bg: ({ value, isArbitrary }, { colors }) => {
1207
- const color = resolveColor(value, colors, isArbitrary);
1208
- if (!color) return null;
1209
- return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1210
- },
1211
- "bg-opacity": ({ value, isArbitrary }, _) => {
1212
- if (!getEffectiveIsWeb()) return null;
1213
- const n = parseFloat(value);
1214
- if (isNaN(n)) return null;
1215
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1216
- return { "--bg-opacity": v };
1217
- },
1218
- // ── Text ───────────────────────────────────────────────────────────────────
1219
- text: ({ value, isArbitrary }, { colors, fontSize }) => {
1220
- const size = resolveFontSize(value, fontSize, false);
1221
- if (!isArbitrary && size !== null) return { fontSize: size };
1222
- if (isArbitrary) {
1223
- if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1224
- return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1225
- }
1226
- return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1227
- }
1228
- const color = resolveColor(value, colors, false);
1229
- if (!color) return null;
1230
- return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1231
- },
1232
- "text-opacity": ({ value, isArbitrary }, _) => {
1233
- if (!getEffectiveIsWeb()) return null;
1234
- const n = parseFloat(value);
1235
- if (isNaN(n)) return null;
1236
- const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
1237
- return { "--text-opacity": v };
1238
- },
1239
- // ── Padding & margin ───────────────────────────────────────────────────────
1240
- // Generated below via makeSpacingResolver() — see PADDING_MARGIN_PROPS.
1241
- // ── Sizing ─────────────────────────────────────────────────────────────────
1242
- w: ({ value, isArbitrary }, { spacing }) => {
1243
- const v = resolveSizing(value, spacing, isArbitrary);
1244
- return v !== null ? { width: v } : null;
1245
- },
1246
- h: ({ value, isArbitrary }, { spacing }) => {
1247
- const v = resolveSizing(value, spacing, isArbitrary);
1248
- return v !== null ? { height: v } : null;
1249
- },
1250
- "min-w": ({ value, isArbitrary }, { spacing }) => {
1251
- const v = resolveSizing(value, spacing, isArbitrary);
1252
- return v !== null ? { minWidth: v } : null;
1253
- },
1254
- "min-h": ({ value, isArbitrary }, { spacing }) => {
1255
- const v = resolveSizing(value, spacing, isArbitrary);
1256
- return v !== null ? { minHeight: v } : null;
1257
- },
1258
- "max-w": ({ value, isArbitrary }, { spacing }) => {
1259
- const v = resolveSizing(value, spacing, isArbitrary);
1260
- return v !== null ? { maxWidth: v } : null;
1261
- },
1262
- "max-h": ({ value, isArbitrary }, { spacing }) => {
1263
- const v = resolveSizing(value, spacing, isArbitrary);
1264
- return v !== null ? { maxHeight: v } : null;
1265
- },
999
+ var BORDER_SIDE_PROPS = {
1000
+ "border-t": ["borderTopWidth", "borderTopColor"],
1001
+ "border-r": ["borderRightWidth", "borderRightColor"],
1002
+ "border-b": ["borderBottomWidth", "borderBottomColor"],
1003
+ "border-l": ["borderLeftWidth", "borderLeftColor"]
1004
+ };
1005
+ var borderResolvers = {
1266
1006
  // ── Border width ───────────────────────────────────────────────────────────
1267
1007
  border: ({ value, isArbitrary }, { colors, borderWidth, spacing }) => {
1268
1008
  if (!value) return { borderWidth: borderWidth["DEFAULT"] ?? 1 };
@@ -1319,188 +1059,128 @@ var RESOLVERS = {
1319
1059
  const r = resolveRadius(value, borderRadius, isArbitrary);
1320
1060
  return r !== null ? { borderBottomRightRadius: r } : null;
1321
1061
  },
1322
- // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1323
- grid: ({ value, isArbitrary }) => {
1324
- if (!value) return null;
1325
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1326
- const n = parseInt(value, 10);
1327
- if (isNaN(n) || n < 1 || n > 12) return null;
1328
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1329
- },
1330
- // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1331
- "grid-cols": ({ value, isArbitrary }) => {
1332
- if (!getEffectiveIsWeb()) return null;
1333
- if (!value) return null;
1334
- if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1335
- if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1336
- const n = parseInt(value, 10);
1337
- if (isNaN(n) || n < 1 || n > 12) return null;
1338
- return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1339
- },
1340
- "grid-rows": ({ value, isArbitrary }) => {
1341
- if (!getEffectiveIsWeb()) return null;
1342
- if (!value) return null;
1343
- if (isArbitrary) return { gridTemplateRows: value };
1344
- if (value === "none") return { gridTemplateRows: "none" };
1345
- const n = parseInt(value, 10);
1346
- if (isNaN(n) || n < 1) return null;
1347
- return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1348
- },
1349
- // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1350
- "grid-flow": ({ value }) => {
1351
- if (!getEffectiveIsWeb()) return null;
1352
- const flows = {
1353
- row: "row",
1354
- col: "column",
1355
- dense: "dense",
1356
- "row-dense": "row dense",
1357
- "col-dense": "column dense"
1358
- };
1359
- const flow = flows[value];
1360
- return flow ? { gridAutoFlow: flow } : null;
1361
- },
1362
- // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1363
- "auto-cols": ({ value, isArbitrary }) => {
1364
- if (!getEffectiveIsWeb()) return null;
1365
- if (isArbitrary) return { gridAutoColumns: value };
1366
- const presets = {
1367
- auto: "auto",
1368
- min: "min-content",
1369
- max: "max-content",
1370
- fr: "minmax(0, 1fr)"
1371
- };
1372
- const v = presets[value];
1373
- return v ? { gridAutoColumns: v } : null;
1374
- },
1375
- "auto-rows": ({ value, isArbitrary }) => {
1376
- if (!getEffectiveIsWeb()) return null;
1377
- if (isArbitrary) return { gridAutoRows: value };
1378
- const presets = {
1379
- auto: "auto",
1380
- min: "min-content",
1381
- max: "max-content",
1382
- fr: "minmax(0, 1fr)"
1383
- };
1384
- const v = presets[value];
1385
- return v ? { gridAutoRows: v } : null;
1386
- },
1387
- // ── Grid column placement (web-only) ──────────────────────────────────────
1388
- "col-span": ({ value }) => {
1389
- if (!getEffectiveIsWeb()) return null;
1390
- if (value === "full") return { gridColumn: "1 / -1" };
1391
- const n = parseInt(value, 10);
1392
- return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1393
- },
1394
- "col-start": ({ value, isArbitrary }) => {
1395
- if (!getEffectiveIsWeb()) return null;
1396
- if (value === "auto") return { gridColumnStart: "auto" };
1397
- if (isArbitrary) return { gridColumnStart: value };
1398
- const n = parseInt(value, 10);
1399
- return isNaN(n) ? null : { gridColumnStart: n };
1400
- },
1401
- "col-end": ({ value, isArbitrary }) => {
1402
- if (!getEffectiveIsWeb()) return null;
1403
- if (value === "auto") return { gridColumnEnd: "auto" };
1404
- if (isArbitrary) return { gridColumnEnd: value };
1405
- const n = parseInt(value, 10);
1406
- return isNaN(n) ? null : { gridColumnEnd: n };
1407
- },
1408
- col: ({ value, isArbitrary }) => {
1409
- if (!getEffectiveIsWeb()) return null;
1410
- if (!value || value === "auto") return { gridColumn: "auto" };
1411
- if (isArbitrary) return { gridColumn: value };
1412
- return null;
1413
- },
1414
- // ── Grid row placement (web-only) ─────────────────────────────────────────
1415
- "row-span": ({ value }) => {
1416
- if (!getEffectiveIsWeb()) return null;
1417
- if (value === "full") return { gridRow: "1 / -1" };
1418
- const n = parseInt(value, 10);
1419
- return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1420
- },
1421
- "row-start": ({ value, isArbitrary }) => {
1422
- if (!getEffectiveIsWeb()) return null;
1423
- if (value === "auto") return { gridRowStart: "auto" };
1424
- if (isArbitrary) return { gridRowStart: value };
1425
- const n = parseInt(value, 10);
1426
- return isNaN(n) ? null : { gridRowStart: n };
1427
- },
1428
- "row-end": ({ value, isArbitrary }) => {
1062
+ // ── Outline extended (web-only) ───────────────────────────────────────────
1063
+ outline: ({ value, isArbitrary }, { colors }) => {
1429
1064
  if (!getEffectiveIsWeb()) return null;
1430
- if (value === "auto") return { gridRowEnd: "auto" };
1431
- if (isArbitrary) return { gridRowEnd: value };
1432
- const n = parseInt(value, 10);
1433
- return isNaN(n) ? null : { gridRowEnd: n };
1065
+ if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1066
+ if (value === "none") return { outline: "none", outlineOffset: "0" };
1067
+ if (isArbitrary) {
1068
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1069
+ return { outlineColor: value };
1070
+ }
1071
+ const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1072
+ if (value in widths) return { outlineWidth: widths[value] };
1073
+ const color = resolveColor(value, colors, false);
1074
+ return color ? { outlineColor: color } : null;
1434
1075
  },
1435
- row: ({ value, isArbitrary }) => {
1076
+ "outline-offset": ({ value, isArbitrary }) => {
1436
1077
  if (!getEffectiveIsWeb()) return null;
1437
- if (!value || value === "auto") return { gridRow: "auto" };
1438
- if (isArbitrary) return { gridRow: value };
1439
- return null;
1078
+ if (isArbitrary) return { outlineOffset: value };
1079
+ const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1080
+ return offsets[value] ? { outlineOffset: offsets[value] } : null;
1440
1081
  },
1441
- // ── Grid alignment (web-only) ──────────────────────────────────────────────
1442
- // place-items is shorthand for align-items + justify-items on a grid container
1443
- "place-items": ({ value }) => {
1082
+ // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1083
+ // These return special __divide* markers that resolver.ts converts to
1084
+ // .cls > * + * { border-... } CSS rules. No inline style is applied.
1085
+ "divide-x": ({ value, isArbitrary }) => {
1444
1086
  if (!getEffectiveIsWeb()) return null;
1445
- const map = {
1446
- start: "start",
1447
- end: "end",
1448
- center: "center",
1449
- stretch: "stretch",
1450
- baseline: "baseline"
1451
- };
1452
- return map[value] ? { placeItems: map[value] } : null;
1087
+ if (value === "reverse") return null;
1088
+ if (!value) return { __divideX: 1 };
1089
+ const n = parseFloat(value);
1090
+ return isNaN(n) ? null : { __divideX: n };
1453
1091
  },
1454
- // place-content is shorthand for align-content + justify-content on a grid container
1455
- "place-content": ({ value }) => {
1092
+ "divide-y": ({ value, isArbitrary }) => {
1456
1093
  if (!getEffectiveIsWeb()) return null;
1457
- const map = {
1458
- start: "start",
1459
- end: "end",
1460
- center: "center",
1461
- stretch: "stretch",
1462
- between: "space-between",
1463
- around: "space-around",
1464
- evenly: "space-evenly",
1465
- baseline: "baseline"
1466
- };
1467
- return map[value] ? { placeContent: map[value] } : null;
1094
+ if (value === "reverse") return null;
1095
+ if (!value) return { __divideY: 1 };
1096
+ const n = parseFloat(value);
1097
+ return isNaN(n) ? null : { __divideY: n };
1468
1098
  },
1469
- // justify-items controls inline-axis alignment of grid items within their cells
1470
- "justify-items": ({ value }) => {
1099
+ divide: ({ value, isArbitrary }, { colors }) => {
1471
1100
  if (!getEffectiveIsWeb()) return null;
1472
- const map = {
1473
- start: "start",
1474
- end: "end",
1475
- center: "center",
1476
- stretch: "stretch"
1101
+ if (isArbitrary) return { __divideColor: value };
1102
+ const styleTokens = {
1103
+ solid: "solid",
1104
+ dashed: "dashed",
1105
+ dotted: "dotted",
1106
+ double: "double",
1107
+ none: "none"
1477
1108
  };
1478
- return map[value] ? { justifyItems: map[value] } : null;
1109
+ if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1110
+ const color = resolveColor(value, colors, false);
1111
+ return color ? { __divideColor: color } : null;
1479
1112
  },
1480
- // place-self is shorthand for align-self + justify-self on a grid item
1481
- "place-self": ({ value }) => {
1482
- if (!getEffectiveIsWeb()) return null;
1483
- const map = {
1484
- auto: "auto",
1485
- start: "start",
1486
- end: "end",
1487
- center: "center",
1488
- stretch: "stretch"
1489
- };
1490
- return map[value] ? { placeSelf: map[value] } : null;
1113
+ // ── Ring ───────────────────────────────────────────────────────────────────
1114
+ // Web: box-shadow outline ring (doesn't affect layout).
1115
+ // Native: React Native has no box-shadow, so this falls back to borderWidth/
1116
+ // borderColor — the closest visual approximation (used by other RN Tailwind-
1117
+ // likes for the same reason). Unlike the web ring, this DOES affect layout,
1118
+ // and it shares its properties with the `border` utility — combining
1119
+ // `border-*` and `ring-*` on the same native element means whichever class
1120
+ // comes later wins, since both ultimately set borderWidth/borderColor.
1121
+ ring: ({ value, isArbitrary }, { colors }) => {
1122
+ const onWeb = getEffectiveIsWeb();
1123
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1124
+ const DEFAULT_WIDTH = 3;
1125
+ const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
1126
+ if (!value) {
1127
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1128
+ }
1129
+ if (value === "inset") {
1130
+ return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
1131
+ }
1132
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1133
+ if (!isArbitrary && value in widthTokens) {
1134
+ const w = widthTokens[value];
1135
+ return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
1136
+ }
1137
+ if (isArbitrary) {
1138
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1139
+ if (numMatch) {
1140
+ const unit = numMatch[2] ?? "px";
1141
+ if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1142
+ return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
1143
+ }
1144
+ return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
1145
+ }
1146
+ const color = resolveColor(value, colors, false);
1147
+ if (color) {
1148
+ return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
1149
+ }
1150
+ return null;
1491
1151
  },
1492
- // justify-self controls inline-axis self-alignment of a grid item
1493
- "justify-self": ({ value }) => {
1152
+ // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
1153
+ // create a gap between the element and the ring. There's no native
1154
+ // equivalent to approximate that with (a border can't create a gap outside
1155
+ // its own element without an extra wrapper view), so this stays a no-op
1156
+ // on native rather than rendering a misleading half-translation.
1157
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1494
1158
  if (!getEffectiveIsWeb()) return null;
1495
- const map = {
1496
- auto: "auto",
1497
- start: "start",
1498
- end: "end",
1499
- center: "center",
1500
- stretch: "stretch"
1159
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1160
+ const DEFAULT_RING_WIDTH = 3;
1161
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1162
+ let offsetWidth;
1163
+ if (!isArbitrary) {
1164
+ offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
1165
+ } else {
1166
+ offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
1167
+ }
1168
+ if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
1169
+ const isZero = offsetWidth === 0 || offsetWidth === "0";
1170
+ const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
1171
+ return {
1172
+ 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}`
1501
1173
  };
1502
- return map[value] ? { justifySelf: map[value] } : null;
1503
- },
1174
+ }
1175
+ };
1176
+ for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
1177
+ borderResolvers[utility] = makeBorderSideResolver(widthProp, colorProp);
1178
+ }
1179
+
1180
+ // src/core/resolvers/filters.ts
1181
+ 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,)";
1182
+ 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,)";
1183
+ var filterResolvers = {
1504
1184
  // ── CSS Filters (composable via CSS variables, web-only) ─────────────────
1505
1185
  blur: ({ value, isArbitrary }) => {
1506
1186
  if (!getEffectiveIsWeb()) return null;
@@ -1635,412 +1315,629 @@ var RESOLVERS = {
1635
1315
  if (value === "none") return { backdropFilter: "none" };
1636
1316
  return null;
1637
1317
  },
1638
- // ── Background gradient (CSS variable gradient stops, web-only) ──────────
1639
- "bg-gradient-to": ({ value }) => {
1640
- if (!getEffectiveIsWeb()) return null;
1641
- const directions = {
1642
- t: "to top",
1643
- tr: "to top right",
1644
- r: "to right",
1645
- br: "to bottom right",
1646
- b: "to bottom",
1647
- bl: "to bottom left",
1648
- l: "to left",
1649
- tl: "to top left"
1650
- };
1651
- const dir = directions[value];
1652
- if (!dir) return null;
1653
- return {
1654
- backgroundImage: `linear-gradient(${dir}, var(--kb-gradient-from, transparent), var(--kb-gradient-stops, transparent))`
1655
- };
1656
- },
1657
- from: ({ value, isArbitrary }, { colors }) => {
1658
- if (!getEffectiveIsWeb()) return null;
1659
- const color = resolveColor(value, colors, isArbitrary);
1660
- if (!color) return null;
1661
- return {
1662
- "--kb-gradient-from": color,
1663
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-to, transparent)`
1664
- };
1665
- },
1666
- via: ({ value, isArbitrary }, { colors }) => {
1667
- if (!getEffectiveIsWeb()) return null;
1668
- const color = resolveColor(value, colors, isArbitrary);
1669
- if (!color) return null;
1670
- return {
1671
- "--kb-gradient-via": color,
1672
- "--kb-gradient-stops": `var(--kb-gradient-from), var(--kb-gradient-via), var(--kb-gradient-to, transparent)`
1673
- };
1674
- },
1675
- to: ({ value, isArbitrary }, { colors }) => {
1676
- if (!getEffectiveIsWeb()) return null;
1677
- const color = resolveColor(value, colors, isArbitrary);
1678
- if (!color) return null;
1679
- return { "--kb-gradient-to": color };
1680
- },
1681
- // ── Transform extras (web-only) ───────────────────────────────────────────
1682
- "skew-x": ({ value, negative, isArbitrary }) => {
1683
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1684
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1685
- return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
1686
- },
1687
- "skew-y": ({ value, negative, isArbitrary }) => {
1688
- const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
1689
- if (!isArbitrary && isNaN(parseFloat(value))) return null;
1690
- return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
1691
- },
1692
- // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
1693
- transform: ({ value, isArbitrary }) => {
1694
- if (!getEffectiveIsWeb()) return null;
1695
- if (isArbitrary) return { transform: value.replace(/_/g, " ") };
1696
- if (value === "none") return { transform: "none" };
1697
- return null;
1698
- },
1699
- // Transform origin
1700
- origin: ({ value, isArbitrary }) => {
1701
- if (!getEffectiveIsWeb()) return null;
1702
- if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
1703
- const origins = {
1704
- center: "center",
1705
- top: "top",
1706
- "top-right": "top right",
1707
- right: "right",
1708
- "bottom-right": "bottom right",
1709
- bottom: "bottom",
1710
- "bottom-left": "bottom left",
1711
- left: "left",
1712
- "top-left": "top left"
1713
- };
1714
- return origins[value] ? { transformOrigin: origins[value] } : null;
1715
- },
1716
- // ── Text decoration advanced (web-only) ───────────────────────────────────
1717
- decoration: ({ value, isArbitrary }, { colors }) => {
1718
- if (!getEffectiveIsWeb()) return null;
1719
- if (isArbitrary) {
1720
- if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
1721
- return { textDecorationColor: value };
1722
- }
1723
- const thickTokens = {
1724
- auto: "auto",
1725
- "from-font": "from-font",
1726
- "0": "0px",
1727
- "1": "1px",
1728
- "2": "2px",
1729
- "4": "4px",
1730
- "8": "8px"
1731
- };
1732
- if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
1733
- const styleTokens = {
1734
- solid: "solid",
1735
- dashed: "dashed",
1736
- dotted: "dotted",
1737
- double: "double",
1738
- wavy: "wavy"
1739
- };
1740
- if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
1741
- const color = resolveColor(value, colors, false);
1742
- return color ? { textDecorationColor: color } : null;
1743
- },
1744
- "underline-offset": ({ value, isArbitrary }) => {
1745
- if (!getEffectiveIsWeb()) return null;
1746
- if (isArbitrary) return { textUnderlineOffset: value };
1747
- const offsets = {
1748
- auto: "auto",
1749
- "0": "0px",
1750
- "1": "1px",
1751
- "2": "2px",
1752
- "4": "4px",
1753
- "8": "8px"
1754
- };
1755
- return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
1756
- },
1757
- // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
1758
- // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
1759
- // the literal string 'you can see it' quotes included), so it passes straight through as a
1760
- // valid `content: '*'` CSS value with no extra wrapping needed.
1761
- content: ({ value, isArbitrary }) => {
1318
+ // ── Mix / background blend mode (web-only) ───────────────────────────────
1319
+ "mix-blend": ({ value }) => {
1762
1320
  if (!getEffectiveIsWeb()) return null;
1763
- if (isArbitrary) return { content: value };
1764
- const presets = { none: "none" };
1765
- return value in presets ? { content: presets[value] } : null;
1766
- },
1767
- // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
1768
- // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
1769
- // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
1770
- // presumably a typo) parses, which is why this only fires when the arbitrary value
1771
- // itself contains a "property:value" pair; anything else (no colon) still resolves
1772
- // to null exactly as before this resolver existed, rather than guessing a property.
1773
- //
1774
- // Only the FIRST colon splits property from value — the value itself may contain
1775
- // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
1776
- // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
1777
- // custom property) key, so it can be used as the style object key as-written with no
1778
- // extra case conversion — it only needs to look like a plausible CSS property name.
1779
- "": ({ value, isArbitrary, negative }) => {
1780
- if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
1781
- if (negative) return null;
1782
- const colonIdx = value.indexOf(":");
1783
- if (colonIdx <= 0) return null;
1784
- const prop = value.slice(0, colonIdx).trim();
1785
- const cssValue = value.slice(colonIdx + 1).trim();
1786
- if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
1787
- return { [prop]: cssValue };
1321
+ const modes = [
1322
+ "normal",
1323
+ "multiply",
1324
+ "screen",
1325
+ "overlay",
1326
+ "darken",
1327
+ "lighten",
1328
+ "color-dodge",
1329
+ "color-burn",
1330
+ "hard-light",
1331
+ "soft-light",
1332
+ "difference",
1333
+ "exclusion",
1334
+ "hue",
1335
+ "saturation",
1336
+ "color",
1337
+ "luminosity",
1338
+ "plus-lighter"
1339
+ ];
1340
+ return modes.includes(value) ? { mixBlendMode: value } : null;
1788
1341
  },
1789
- // ── Outline extended (web-only) ───────────────────────────────────────────
1790
- outline: ({ value, isArbitrary }, { colors }) => {
1342
+ "bg-blend": ({ value }) => {
1791
1343
  if (!getEffectiveIsWeb()) return null;
1792
- if (!value) return { outline: "2px solid transparent", outlineOffset: "2px" };
1793
- if (value === "none") return { outline: "none", outlineOffset: "0" };
1794
- if (isArbitrary) {
1795
- if (/^\d/.test(value) || value.startsWith("calc(")) return { outlineWidth: value };
1796
- return { outlineColor: value };
1797
- }
1798
- const widths = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1799
- if (value in widths) return { outlineWidth: widths[value] };
1800
- const color = resolveColor(value, colors, false);
1801
- return color ? { outlineColor: color } : null;
1344
+ const modes = [
1345
+ "normal",
1346
+ "multiply",
1347
+ "screen",
1348
+ "overlay",
1349
+ "darken",
1350
+ "lighten",
1351
+ "color-dodge",
1352
+ "color-burn",
1353
+ "hard-light",
1354
+ "soft-light",
1355
+ "difference",
1356
+ "exclusion",
1357
+ "hue",
1358
+ "saturation",
1359
+ "color",
1360
+ "luminosity"
1361
+ ];
1362
+ return modes.includes(value) ? { backgroundBlendMode: value } : null;
1802
1363
  },
1803
- "outline-offset": ({ value, isArbitrary }) => {
1364
+ // ── Will-change (web-only) ─────────────────────────────────────────────────
1365
+ "will-change": ({ value, isArbitrary }) => {
1804
1366
  if (!getEffectiveIsWeb()) return null;
1805
- if (isArbitrary) return { outlineOffset: value };
1806
- const offsets = { "0": "0px", "1": "1px", "2": "2px", "4": "4px", "8": "8px" };
1807
- return offsets[value] ? { outlineOffset: offsets[value] } : null;
1367
+ if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1368
+ const presets = {
1369
+ auto: "auto",
1370
+ scroll: "scroll-position",
1371
+ contents: "contents",
1372
+ transform: "transform"
1373
+ };
1374
+ return presets[value] ? { willChange: presets[value] } : null;
1375
+ }
1376
+ };
1377
+
1378
+ // src/core/resolvers/layout.ts
1379
+ var _standalone = null;
1380
+ function buildStandalone() {
1381
+ const web = getEffectiveIsWeb();
1382
+ return {
1383
+ // Display
1384
+ // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
1385
+ // a no-op normally, but is needed to re-show an element that was hidden via `hidden`.
1386
+ // `grid`, `contents`, `flow-root` have no native equivalent and stay null there.
1387
+ // The inline-* family (inline, inline-block, inline-flex, inline-grid) falls back
1388
+ // to 'flex' on native instead — like plain `flex`, this is a no-op most of the
1389
+ // time, but without it these utilities couldn't re-show a `hidden` element on
1390
+ // native either (null → no style applied → still display:'none').
1391
+ flex: { display: "flex" },
1392
+ block: web ? { display: "block" } : null,
1393
+ "inline-block": { display: web ? "inline-block" : "flex" },
1394
+ inline: { display: web ? "inline" : "flex" },
1395
+ grid: web ? { display: "grid" } : null,
1396
+ grd: web ? { display: "grid" } : null,
1397
+ "inline-flex": { display: web ? "inline-flex" : "flex" },
1398
+ "inline-grid": { display: web ? "inline-grid" : "flex" },
1399
+ hidden: { display: "none" },
1400
+ contents: web ? { display: "contents" } : null,
1401
+ "flow-root": web ? { display: "flow-root" } : null,
1402
+ // Flex direction, wrap, grow/shrink, and alignment (both web and native) —
1403
+ // every one of these is meaningless without the element also being a flex
1404
+ // container. React Native Views are ALWAYS flex containers by default, so
1405
+ // on native this was already true for free; on web a plain <div> defaults
1406
+ // to display:block, so e.g. `items-center` alone silently did nothing.
1407
+ // Folding `display: 'flex'` into these on web closes that native/web gap —
1408
+ // `flex-1 items-center justify-center` now behaves the same on both,
1409
+ // without also needing a separate `flex` class.
1410
+ "flex-row": { flexDirection: "row", ...web ? { display: "flex" } : {} },
1411
+ "flex-col": { flexDirection: "column", ...web ? { display: "flex" } : {} },
1412
+ "flex-row-reverse": { flexDirection: "row-reverse", ...web ? { display: "flex" } : {} },
1413
+ "flex-col-reverse": { flexDirection: "column-reverse", ...web ? { display: "flex" } : {} },
1414
+ // Flex wrap (both)
1415
+ "flex-wrap": { flexWrap: "wrap", ...web ? { display: "flex" } : {} },
1416
+ "flex-wrap-reverse": { flexWrap: "wrap-reverse", ...web ? { display: "flex" } : {} },
1417
+ "flex-nowrap": { flexWrap: "nowrap", ...web ? { display: "flex" } : {} },
1418
+ // Flex grow / shrink (both) — deliberately NOT folding in display:flex
1419
+ // like the container-level properties above. flex-grow/flex-shrink are
1420
+ // item-level: they already take effect purely via the PARENT already
1421
+ // being a flex container, with zero dependency on this element's own
1422
+ // display. Forcing display:flex here would instead be an unrelated side
1423
+ // effect on THIS element's own children (turning what may be an ordinary
1424
+ // block stack into a flex row) — exactly the kind of surprise the
1425
+ // container-level properties above can never cause, since those are
1426
+ // no-ops without display:flex to begin with.
1427
+ "flex-grow": { flexGrow: 1 },
1428
+ "flex-grow-0": { flexGrow: 0 },
1429
+ "flex-shrink": { flexShrink: 1 },
1430
+ "flex-shrink-0": { flexShrink: 0 },
1431
+ // Align items (both)
1432
+ "items-start": { alignItems: "flex-start", ...web ? { display: "flex" } : {} },
1433
+ "items-end": { alignItems: "flex-end", ...web ? { display: "flex" } : {} },
1434
+ "items-center": { alignItems: "center", ...web ? { display: "flex" } : {} },
1435
+ "items-baseline": { alignItems: "baseline", ...web ? { display: "flex" } : {} },
1436
+ "items-stretch": { alignItems: "stretch", ...web ? { display: "flex" } : {} },
1437
+ // Justify content (both)
1438
+ "justify-start": { justifyContent: "flex-start", ...web ? { display: "flex" } : {} },
1439
+ "justify-end": { justifyContent: "flex-end", ...web ? { display: "flex" } : {} },
1440
+ "justify-center": { justifyContent: "center", ...web ? { display: "flex" } : {} },
1441
+ "justify-between": { justifyContent: "space-between", ...web ? { display: "flex" } : {} },
1442
+ "justify-around": { justifyContent: "space-around", ...web ? { display: "flex" } : {} },
1443
+ "justify-evenly": { justifyContent: "space-evenly", ...web ? { display: "flex" } : {} },
1444
+ // Align content (both)
1445
+ "content-start": { alignContent: "flex-start", ...web ? { display: "flex" } : {} },
1446
+ "content-end": { alignContent: "flex-end", ...web ? { display: "flex" } : {} },
1447
+ "content-center": { alignContent: "center", ...web ? { display: "flex" } : {} },
1448
+ "content-between": { alignContent: "space-between", ...web ? { display: "flex" } : {} },
1449
+ "content-around": { alignContent: "space-around", ...web ? { display: "flex" } : {} },
1450
+ "content-evenly": { alignContent: "space-evenly", ...web ? { display: "flex" } : {} },
1451
+ "content-stretch": { alignContent: "stretch", ...web ? { display: "flex" } : {} },
1452
+ // Align self (both)
1453
+ "self-auto": { alignSelf: "auto" },
1454
+ "self-start": { alignSelf: "flex-start" },
1455
+ "self-end": { alignSelf: "flex-end" },
1456
+ "self-center": { alignSelf: "center" },
1457
+ "self-stretch": { alignSelf: "stretch" },
1458
+ "self-baseline": { alignSelf: "baseline" },
1459
+ // Text align (both)
1460
+ "text-left": { textAlign: "left" },
1461
+ "text-right": { textAlign: "right" },
1462
+ "text-center": { textAlign: "center" },
1463
+ "text-justify": { textAlign: "justify" },
1464
+ // Font weight shortcuts (both)
1465
+ "font-thin": { fontWeight: "100" },
1466
+ "font-extralight": { fontWeight: "200" },
1467
+ "font-light": { fontWeight: "300" },
1468
+ "font-normal": { fontWeight: "400" },
1469
+ "font-medium": { fontWeight: "500" },
1470
+ "font-semibold": { fontWeight: "600" },
1471
+ "font-bold": { fontWeight: "700" },
1472
+ "font-extrabold": { fontWeight: "800" },
1473
+ "font-black": { fontWeight: "900" },
1474
+ // Text decoration (overline is web-only)
1475
+ underline: { textDecorationLine: "underline" },
1476
+ overline: web ? { textDecorationLine: "overline" } : null,
1477
+ "line-through": { textDecorationLine: "line-through" },
1478
+ "no-underline": { textDecorationLine: "none" },
1479
+ // Text transform (both)
1480
+ uppercase: { textTransform: "uppercase" },
1481
+ lowercase: { textTransform: "lowercase" },
1482
+ capitalize: { textTransform: "capitalize" },
1483
+ "normal-case": { textTransform: "none" },
1484
+ // Font style (both)
1485
+ italic: { fontStyle: "italic" },
1486
+ "non-italic": { fontStyle: "normal" },
1487
+ // Position (fixed/sticky/static are web-only; RN only supports relative/absolute)
1488
+ relative: { position: "relative" },
1489
+ absolute: { position: "absolute" },
1490
+ fixed: web ? { position: "fixed" } : null,
1491
+ sticky: web ? { position: "sticky" } : null,
1492
+ static: web ? { position: "static" } : null,
1493
+ // Visibility (no `visibility` prop on native; use opacity instead)
1494
+ visible: web ? { visibility: "visible" } : null,
1495
+ invisible: web ? { visibility: "hidden" } : { opacity: 0 },
1496
+ // Overflow (RN supports 'hidden' | 'visible' | 'scroll'; not 'auto')
1497
+ "overflow-hidden": { overflow: "hidden" },
1498
+ "overflow-visible": { overflow: "visible" },
1499
+ "overflow-scroll": { overflow: "scroll" },
1500
+ "overflow-auto": web ? { overflow: "auto" } : { overflow: "scroll" },
1501
+ // overflowX / overflowY are web-only
1502
+ "overflow-x-hidden": web ? { overflowX: "hidden" } : null,
1503
+ "overflow-x-scroll": web ? { overflowX: "scroll" } : null,
1504
+ "overflow-x-auto": web ? { overflowX: "auto" } : null,
1505
+ "overflow-y-hidden": web ? { overflowY: "hidden" } : null,
1506
+ "overflow-y-scroll": web ? { overflowY: "scroll" } : null,
1507
+ "overflow-y-auto": web ? { overflowY: "auto" } : null,
1508
+ // Object fit (web-only; use resizeMode prop on RN Image)
1509
+ "object-contain": web ? { objectFit: "contain" } : null,
1510
+ "object-cover": web ? { objectFit: "cover" } : null,
1511
+ "object-fill": web ? { objectFit: "fill" } : null,
1512
+ "object-none": web ? { objectFit: "none" } : null,
1513
+ "object-scale-down": web ? { objectFit: "scale-down" } : null,
1514
+ // Border style (both)
1515
+ "border-solid": { borderStyle: "solid" },
1516
+ "border-dashed": { borderStyle: "dashed" },
1517
+ "border-dotted": { borderStyle: "dotted" },
1518
+ "border-none": { borderWidth: 0 },
1519
+ // Border sides default width (both)
1520
+ "border-t": { borderTopWidth: 1 },
1521
+ "border-r": { borderRightWidth: 1 },
1522
+ "border-b": { borderBottomWidth: 1 },
1523
+ "border-l": { borderLeftWidth: 1 },
1524
+ // Cursor (web-only)
1525
+ "cursor-auto": web ? { cursor: "auto" } : null,
1526
+ "cursor-default": web ? { cursor: "default" } : null,
1527
+ "cursor-pointer": web ? { cursor: "pointer" } : null,
1528
+ "cursor-wait": web ? { cursor: "wait" } : null,
1529
+ "cursor-text": web ? { cursor: "text" } : null,
1530
+ "cursor-move": web ? { cursor: "move" } : null,
1531
+ "cursor-not-allowed": web ? { cursor: "not-allowed" } : null,
1532
+ // User select (web-only)
1533
+ "select-none": web ? { userSelect: "none" } : null,
1534
+ "select-text": web ? { userSelect: "text" } : null,
1535
+ "select-all": web ? { userSelect: "all" } : null,
1536
+ "select-auto": web ? { userSelect: "auto" } : null,
1537
+ // Pointer events (both)
1538
+ "pointer-events-none": { pointerEvents: "none" },
1539
+ "pointer-events-auto": { pointerEvents: "auto" },
1540
+ // Whitespace (web-only; RN Text uses numberOfLines prop instead)
1541
+ "whitespace-normal": web ? { whiteSpace: "normal" } : null,
1542
+ "whitespace-nowrap": web ? { whiteSpace: "nowrap" } : null,
1543
+ "whitespace-pre": web ? { whiteSpace: "pre" } : null,
1544
+ "whitespace-pre-wrap": web ? { whiteSpace: "pre-wrap" } : null,
1545
+ "whitespace-pre-line": web ? { whiteSpace: "pre-line" } : null,
1546
+ // Word break (web-only)
1547
+ "break-normal": web ? { overflowWrap: "normal", wordBreak: "normal" } : null,
1548
+ "break-words": web ? { overflowWrap: "break-word" } : null,
1549
+ "break-all": web ? { wordBreak: "break-all" } : null,
1550
+ // Misc web-only
1551
+ truncate: web ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } : null,
1552
+ "box-border": web ? { boxSizing: "border-box" } : null,
1553
+ "box-content": web ? { boxSizing: "content-box" } : null,
1554
+ "appearance-none": web ? { appearance: "none" } : null,
1555
+ "outline-none": web ? { outline: "none", outlineOffset: "0" } : null,
1556
+ outline: web ? { outline: "2px solid transparent", outlineOffset: "2px" } : null,
1557
+ resize: web ? { resize: "both" } : null,
1558
+ "resize-none": web ? { resize: "none" } : null,
1559
+ "resize-y": web ? { resize: "vertical" } : null,
1560
+ "resize-x": web ? { resize: "horizontal" } : null,
1561
+ antialiased: web ? { WebkitFontSmoothing: "antialiased", MozOsxFontSmoothing: "grayscale" } : null,
1562
+ "subpixel-antialiased": web ? { WebkitFontSmoothing: "subpixel-antialiased", MozOsxFontSmoothing: "auto" } : null,
1563
+ "overflow-ellipsis": web ? { textOverflow: "ellipsis" } : null,
1564
+ "sr-only": web ? {
1565
+ position: "absolute",
1566
+ width: 1,
1567
+ height: 1,
1568
+ padding: 0,
1569
+ margin: -1,
1570
+ overflow: "hidden",
1571
+ clip: "rect(0,0,0,0)",
1572
+ whiteSpace: "nowrap",
1573
+ border: 0
1574
+ } : null,
1575
+ "not-sr-only": web ? {
1576
+ position: "static",
1577
+ width: "auto",
1578
+ height: "auto",
1579
+ padding: 0,
1580
+ margin: 0,
1581
+ overflow: "visible",
1582
+ clip: "auto",
1583
+ whiteSpace: "normal"
1584
+ } : null,
1585
+ // Lists (web-only)
1586
+ "list-none": web ? { listStyleType: "none" } : null,
1587
+ "list-disc": web ? { listStyleType: "disc" } : null,
1588
+ "list-decimal": web ? { listStyleType: "decimal" } : null,
1589
+ // Background clip (web-only)
1590
+ "bg-clip-border": web ? { backgroundClip: "border-box" } : null,
1591
+ "bg-clip-padding": web ? { backgroundClip: "padding-box" } : null,
1592
+ "bg-clip-content": web ? { backgroundClip: "content-box" } : null,
1593
+ "bg-clip-text": web ? { backgroundClip: "text", WebkitBackgroundClip: "text" } : null,
1594
+ // Background image
1595
+ "bg-none": web ? { backgroundImage: "none" } : null,
1596
+ // Background size (web-only)
1597
+ "bg-auto": web ? { backgroundSize: "auto" } : null,
1598
+ "bg-cover": web ? { backgroundSize: "cover" } : null,
1599
+ "bg-contain": web ? { backgroundSize: "contain" } : null,
1600
+ // Background position (web-only)
1601
+ "bg-center": web ? { backgroundPosition: "center" } : null,
1602
+ "bg-top": web ? { backgroundPosition: "top" } : null,
1603
+ "bg-bottom": web ? { backgroundPosition: "bottom" } : null,
1604
+ "bg-left": web ? { backgroundPosition: "left" } : null,
1605
+ "bg-right": web ? { backgroundPosition: "right" } : null,
1606
+ "bg-left-top": web ? { backgroundPosition: "left top" } : null,
1607
+ "bg-left-bottom": web ? { backgroundPosition: "left bottom" } : null,
1608
+ "bg-right-top": web ? { backgroundPosition: "right top" } : null,
1609
+ "bg-right-bottom": web ? { backgroundPosition: "right bottom" } : null,
1610
+ // Background repeat (web-only)
1611
+ "bg-repeat": web ? { backgroundRepeat: "repeat" } : null,
1612
+ "bg-no-repeat": web ? { backgroundRepeat: "no-repeat" } : null,
1613
+ "bg-repeat-x": web ? { backgroundRepeat: "repeat-x" } : null,
1614
+ "bg-repeat-y": web ? { backgroundRepeat: "repeat-y" } : null,
1615
+ "bg-repeat-round": web ? { backgroundRepeat: "round" } : null,
1616
+ "bg-repeat-space": web ? { backgroundRepeat: "space" } : null,
1617
+ // Background attachment (web-only)
1618
+ "bg-fixed": web ? { backgroundAttachment: "fixed" } : null,
1619
+ "bg-local": web ? { backgroundAttachment: "local" } : null,
1620
+ "bg-scroll": web ? { backgroundAttachment: "scroll" } : null,
1621
+ // Object position (web-only)
1622
+ "object-center": web ? { objectPosition: "center" } : null,
1623
+ "object-top": web ? { objectPosition: "top" } : null,
1624
+ "object-bottom": web ? { objectPosition: "bottom" } : null,
1625
+ "object-left": web ? { objectPosition: "left" } : null,
1626
+ "object-right": web ? { objectPosition: "right" } : null,
1627
+ "object-left-top": web ? { objectPosition: "left top" } : null,
1628
+ "object-left-bottom": web ? { objectPosition: "left bottom" } : null,
1629
+ "object-right-top": web ? { objectPosition: "right top" } : null,
1630
+ "object-right-bottom": web ? { objectPosition: "right bottom" } : null,
1631
+ // Table (web-only)
1632
+ table: web ? { display: "table" } : null,
1633
+ "table-auto": web ? { tableLayout: "auto" } : null,
1634
+ "table-fixed": web ? { tableLayout: "fixed" } : null,
1635
+ "caption-top": web ? { captionSide: "top" } : null,
1636
+ "caption-bottom": web ? { captionSide: "bottom" } : null,
1637
+ "border-collapse": web ? { borderCollapse: "collapse" } : null,
1638
+ "border-separate": web ? { borderCollapse: "separate" } : null,
1639
+ // List style position (web-only)
1640
+ "list-inside": web ? { listStylePosition: "inside" } : null,
1641
+ "list-outside": web ? { listStylePosition: "outside" } : null,
1642
+ // Font variant numeric (web-only)
1643
+ "normal-nums": web ? { fontVariantNumeric: "normal" } : null,
1644
+ ordinal: web ? { fontVariantNumeric: "ordinal" } : null,
1645
+ "slashed-zero": web ? { fontVariantNumeric: "slashed-zero" } : null,
1646
+ "lining-nums": web ? { fontVariantNumeric: "lining-nums" } : null,
1647
+ "oldstyle-nums": web ? { fontVariantNumeric: "oldstyle-nums" } : null,
1648
+ "proportional-nums": web ? { fontVariantNumeric: "proportional-nums" } : null,
1649
+ "tabular-nums": web ? { fontVariantNumeric: "tabular-nums" } : null,
1650
+ "diagonal-fractions": web ? { fontVariantNumeric: "diagonal-fractions" } : null,
1651
+ "stacked-fractions": web ? { fontVariantNumeric: "stacked-fractions" } : null,
1652
+ // Isolation (web-only)
1653
+ isolate: web ? { isolation: "isolate" } : null,
1654
+ "isolation-auto": web ? { isolation: "auto" } : null,
1655
+ // Backface visibility (both iOS and Android)
1656
+ "backface-visible": { backfaceVisibility: "visible" },
1657
+ "backface-hidden": { backfaceVisibility: "hidden" },
1658
+ // CSS filters — standalone = default/full effect (web-only)
1659
+ grayscale: web ? { "--kb-grayscale": "grayscale(100%)", filter: FILTER_COMPOSE } : null,
1660
+ invert: web ? { "--kb-invert": "invert(100%)", filter: FILTER_COMPOSE } : null,
1661
+ sepia: web ? { "--kb-sepia": "sepia(100%)", filter: FILTER_COMPOSE } : null,
1662
+ // Divide none
1663
+ "divide-none": web ? { __divideX: 0, __divideY: 0 } : null,
1664
+ // Group / peer markers (no-op: no style emitted; just ensures isKnownUtility returns true)
1665
+ group: web ? {} : null,
1666
+ peer: web ? {} : null,
1667
+ // Text-wrap (web-only)
1668
+ "text-wrap": web ? { textWrap: "wrap" } : null,
1669
+ "text-nowrap": web ? { textWrap: "nowrap" } : null,
1670
+ "text-balance": web ? { textWrap: "balance" } : null,
1671
+ "text-pretty": web ? { textWrap: "pretty" } : null,
1672
+ // Screen sizing — dvw/dvh (dynamic viewport units) instead of vw/vh: on mobile
1673
+ // browsers, vh/vw are pinned to the LARGEST viewport size (address bar hidden),
1674
+ // so `h-screen` overflows behind the address bar when it's shown. dvh/dvw track
1675
+ // the actual visible viewport as browser chrome shows/hides. Desktop behavior
1676
+ // is unchanged since there's no dynamic chrome to account for.
1677
+ // (vw/dvw values are web-only; vh/dvh values work cross-platform via spacing scale for h-*)
1678
+ "w-screen": web ? { width: "100dvw" } : null,
1679
+ "min-h-screen": { minHeight: "100dvh" },
1680
+ "max-h-screen": { maxHeight: "100dvh" },
1681
+ "min-w-screen": web ? { minWidth: "100dvw" } : null,
1682
+ "max-w-screen": web ? { maxWidth: "100dvw" } : null,
1683
+ // max-w named container sizes (mirrors Tailwind's container scale)
1684
+ "max-w-none": { maxWidth: "none" },
1685
+ "max-w-xs": { maxWidth: 320 },
1686
+ "max-w-sm": { maxWidth: 384 },
1687
+ "max-w-md": { maxWidth: 448 },
1688
+ "max-w-lg": { maxWidth: 512 },
1689
+ "max-w-xl": { maxWidth: 576 },
1690
+ "max-w-2xl": { maxWidth: 672 },
1691
+ "max-w-3xl": { maxWidth: 768 },
1692
+ "max-w-4xl": { maxWidth: 896 },
1693
+ "max-w-5xl": { maxWidth: 1024 },
1694
+ "max-w-6xl": { maxWidth: 1152 },
1695
+ "max-w-7xl": { maxWidth: 1280 },
1696
+ "max-w-prose": web ? { maxWidth: "65ch" } : null,
1697
+ // Extended cursors (web-only)
1698
+ "cursor-grab": web ? { cursor: "grab" } : null,
1699
+ "cursor-grabbing": web ? { cursor: "grabbing" } : null,
1700
+ "cursor-zoom-in": web ? { cursor: "zoom-in" } : null,
1701
+ "cursor-zoom-out": web ? { cursor: "zoom-out" } : null,
1702
+ "cursor-crosshair": web ? { cursor: "crosshair" } : null,
1703
+ "cursor-help": web ? { cursor: "help" } : null,
1704
+ "cursor-none": web ? { cursor: "none" } : null,
1705
+ // Overflow clip (web-only)
1706
+ "overflow-clip": web ? { overflow: "clip" } : null,
1707
+ "overflow-x-clip": web ? { overflowX: "clip" } : null,
1708
+ "overflow-y-clip": web ? { overflowY: "clip" } : null,
1709
+ // Scroll behavior (web-only)
1710
+ "scroll-smooth": web ? { scrollBehavior: "smooth" } : null,
1711
+ "scroll-auto": web ? { scrollBehavior: "auto" } : null,
1712
+ // Float (web-only)
1713
+ "float-left": web ? { float: "left" } : null,
1714
+ "float-right": web ? { float: "right" } : null,
1715
+ "float-start": web ? { float: "inline-start" } : null,
1716
+ "float-end": web ? { float: "inline-end" } : null,
1717
+ "float-none": web ? { float: "none" } : null,
1718
+ // Clear (web-only)
1719
+ "clear-left": web ? { clear: "left" } : null,
1720
+ "clear-right": web ? { clear: "right" } : null,
1721
+ "clear-both": web ? { clear: "both" } : null,
1722
+ "clear-start": web ? { clear: "inline-start" } : null,
1723
+ "clear-end": web ? { clear: "inline-end" } : null,
1724
+ "clear-none": web ? { clear: "none" } : null,
1725
+ // Vertical align (web-only)
1726
+ "align-baseline": web ? { verticalAlign: "baseline" } : null,
1727
+ "align-top": web ? { verticalAlign: "top" } : null,
1728
+ "align-middle": web ? { verticalAlign: "middle" } : null,
1729
+ "align-bottom": web ? { verticalAlign: "bottom" } : null,
1730
+ "align-text-top": web ? { verticalAlign: "text-top" } : null,
1731
+ "align-text-bottom": web ? { verticalAlign: "text-bottom" } : null,
1732
+ "align-sub": web ? { verticalAlign: "sub" } : null,
1733
+ "align-super": web ? { verticalAlign: "super" } : null,
1734
+ // Touch action (web-only)
1735
+ "touch-auto": web ? { touchAction: "auto" } : null,
1736
+ "touch-none": web ? { touchAction: "none" } : null,
1737
+ "touch-pan-x": web ? { touchAction: "pan-x" } : null,
1738
+ "touch-pan-y": web ? { touchAction: "pan-y" } : null,
1739
+ "touch-pan-left": web ? { touchAction: "pan-left" } : null,
1740
+ "touch-pan-right": web ? { touchAction: "pan-right" } : null,
1741
+ "touch-pan-up": web ? { touchAction: "pan-up" } : null,
1742
+ "touch-pan-down": web ? { touchAction: "pan-down" } : null,
1743
+ "touch-pinch-zoom": web ? { touchAction: "pinch-zoom" } : null,
1744
+ "touch-manipulation": web ? { touchAction: "manipulation" } : null
1745
+ };
1746
+ }
1747
+ function getStandalone() {
1748
+ if (!_standalone) _standalone = buildStandalone();
1749
+ return _standalone;
1750
+ }
1751
+ var layoutResolvers = {
1752
+ // ── Grid (responsive column count: grid-1 … grid-12, or arbitrary) ───────
1753
+ grid: ({ value, isArbitrary }) => {
1754
+ if (!value) return null;
1755
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1756
+ const n = parseInt(value, 10);
1757
+ if (isNaN(n) || n < 1 || n > 12) return null;
1758
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1808
1759
  },
1809
- // ── Columns (web-only) ─────────────────────────────────────────────────────
1810
- columns: ({ value, isArbitrary }) => {
1760
+ // ── Grid template columns/rows (Tailwind-style aliases, web-only) ──────────
1761
+ "grid-cols": ({ value, isArbitrary }) => {
1811
1762
  if (!getEffectiveIsWeb()) return null;
1812
- if (isArbitrary) return { columnCount: value };
1813
- if (value === "auto") return { columnCount: "auto" };
1763
+ if (!value) return null;
1764
+ if (isArbitrary) return { display: "grid", gridTemplateColumns: value };
1765
+ if (value === "none") return { display: "grid", gridTemplateColumns: "none" };
1814
1766
  const n = parseInt(value, 10);
1815
- if (!isNaN(n)) return { columnCount: n };
1816
- const widths = {
1817
- "3xs": "16rem",
1818
- "2xs": "18rem",
1819
- xs: "20rem",
1820
- sm: "24rem",
1821
- md: "28rem",
1822
- lg: "32rem",
1823
- xl: "36rem",
1824
- "2xl": "42rem",
1825
- "3xl": "48rem",
1826
- "4xl": "56rem",
1827
- "5xl": "64rem",
1828
- "6xl": "72rem",
1829
- "7xl": "80rem"
1830
- };
1831
- return widths[value] ? { columnWidth: widths[value] } : null;
1767
+ if (isNaN(n) || n < 1 || n > 12) return null;
1768
+ return { display: "grid", gridTemplateColumns: `repeat(${n}, minmax(0, 1fr))` };
1832
1769
  },
1833
- // ── Mix / background blend mode (web-only) ───────────────────────────────
1834
- "mix-blend": ({ value }) => {
1770
+ "grid-rows": ({ value, isArbitrary }) => {
1835
1771
  if (!getEffectiveIsWeb()) return null;
1836
- const modes = [
1837
- "normal",
1838
- "multiply",
1839
- "screen",
1840
- "overlay",
1841
- "darken",
1842
- "lighten",
1843
- "color-dodge",
1844
- "color-burn",
1845
- "hard-light",
1846
- "soft-light",
1847
- "difference",
1848
- "exclusion",
1849
- "hue",
1850
- "saturation",
1851
- "color",
1852
- "luminosity",
1853
- "plus-lighter"
1854
- ];
1855
- return modes.includes(value) ? { mixBlendMode: value } : null;
1772
+ if (!value) return null;
1773
+ if (isArbitrary) return { gridTemplateRows: value };
1774
+ if (value === "none") return { gridTemplateRows: "none" };
1775
+ const n = parseInt(value, 10);
1776
+ if (isNaN(n) || n < 1) return null;
1777
+ return { gridTemplateRows: `repeat(${n}, minmax(0, 1fr))` };
1856
1778
  },
1857
- "bg-blend": ({ value }) => {
1779
+ // ── Grid auto flow (web-only) ─────────────────────────────────────────────
1780
+ "grid-flow": ({ value }) => {
1858
1781
  if (!getEffectiveIsWeb()) return null;
1859
- const modes = [
1860
- "normal",
1861
- "multiply",
1862
- "screen",
1863
- "overlay",
1864
- "darken",
1865
- "lighten",
1866
- "color-dodge",
1867
- "color-burn",
1868
- "hard-light",
1869
- "soft-light",
1870
- "difference",
1871
- "exclusion",
1872
- "hue",
1873
- "saturation",
1874
- "color",
1875
- "luminosity"
1876
- ];
1877
- return modes.includes(value) ? { backgroundBlendMode: value } : null;
1782
+ const flows = {
1783
+ row: "row",
1784
+ col: "column",
1785
+ dense: "dense",
1786
+ "row-dense": "row dense",
1787
+ "col-dense": "column dense"
1788
+ };
1789
+ const flow = flows[value];
1790
+ return flow ? { gridAutoFlow: flow } : null;
1878
1791
  },
1879
- // ── Will-change (web-only) ─────────────────────────────────────────────────
1880
- "will-change": ({ value, isArbitrary }) => {
1792
+ // ── Grid auto sizing (web-only) ───────────────────────────────────────────
1793
+ "auto-cols": ({ value, isArbitrary }) => {
1881
1794
  if (!getEffectiveIsWeb()) return null;
1882
- if (isArbitrary) return { willChange: value.replace(/_/g, ", ") };
1795
+ if (isArbitrary) return { gridAutoColumns: value };
1883
1796
  const presets = {
1884
1797
  auto: "auto",
1885
- scroll: "scroll-position",
1886
- contents: "contents",
1887
- transform: "transform"
1798
+ min: "min-content",
1799
+ max: "max-content",
1800
+ fr: "minmax(0, 1fr)"
1888
1801
  };
1889
- return presets[value] ? { willChange: presets[value] } : null;
1890
- },
1891
- // ── Divide (child combinator CSS, web-only) ───────────────────────────────
1892
- // These return special __divide* markers that resolver.ts converts to
1893
- // .cls > * + * { border-... } CSS rules. No inline style is applied.
1894
- "divide-x": ({ value, isArbitrary }) => {
1895
- if (!getEffectiveIsWeb()) return null;
1896
- if (value === "reverse") return null;
1897
- if (!value) return { __divideX: 1 };
1898
- const n = parseFloat(value);
1899
- return isNaN(n) ? null : { __divideX: n };
1900
- },
1901
- "divide-y": ({ value, isArbitrary }) => {
1902
- if (!getEffectiveIsWeb()) return null;
1903
- if (value === "reverse") return null;
1904
- if (!value) return { __divideY: 1 };
1905
- const n = parseFloat(value);
1906
- return isNaN(n) ? null : { __divideY: n };
1802
+ const v = presets[value];
1803
+ return v ? { gridAutoColumns: v } : null;
1907
1804
  },
1908
- divide: ({ value, isArbitrary }, { colors }) => {
1805
+ "auto-rows": ({ value, isArbitrary }) => {
1909
1806
  if (!getEffectiveIsWeb()) return null;
1910
- if (isArbitrary) return { __divideColor: value };
1911
- const styleTokens = {
1912
- solid: "solid",
1913
- dashed: "dashed",
1914
- dotted: "dotted",
1915
- double: "double",
1916
- none: "none"
1917
- };
1918
- if (value in styleTokens) return { __divideStyle: styleTokens[value] };
1919
- const color = resolveColor(value, colors, false);
1920
- return color ? { __divideColor: color } : null;
1921
- },
1922
- // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
1923
- // Named presets give cross-platform shadow; arbitrary is web-only.
1924
- "text-shadow": ({ value, isArbitrary }, { colors }) => {
1925
- if (value === "none") {
1926
- return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
1927
- }
1928
- const webPresets = {
1929
- "": "0 2px 4px rgba(0,0,0,0.3)",
1930
- sm: "0 1px 2px rgba(0,0,0,0.3)",
1931
- md: "0 4px 6px rgba(0,0,0,0.3)",
1932
- lg: "0 8px 16px rgba(0,0,0,0.5)",
1933
- xl: "0 16px 32px rgba(0,0,0,0.5)"
1934
- };
1935
- const nativePresets = {
1936
- "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
1937
- sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
1938
- md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
1939
- lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
1940
- xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
1807
+ if (isArbitrary) return { gridAutoRows: value };
1808
+ const presets = {
1809
+ auto: "auto",
1810
+ min: "min-content",
1811
+ max: "max-content",
1812
+ fr: "minmax(0, 1fr)"
1941
1813
  };
1942
- if (value in webPresets) {
1943
- if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
1944
- return nativePresets[value] ?? null;
1945
- }
1946
- if (isArbitrary) {
1947
- return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
1948
- }
1949
- const color = resolveColor(value, colors, false);
1950
- if (color) {
1951
- return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
1952
- }
1953
- return null;
1814
+ const v = presets[value];
1815
+ return v ? { gridAutoRows: v } : null;
1954
1816
  },
1955
- // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
1956
- perspective: ({ value, isArbitrary }) => {
1957
- if (isArbitrary) {
1958
- const numPx = parseFloat(value);
1959
- if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
1960
- return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
1961
- }
1962
- if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
1963
- const n = parseFloat(value);
1964
- if (isNaN(n)) return null;
1965
- return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
1817
+ // ── Grid column placement (web-only) ──────────────────────────────────────
1818
+ "col-span": ({ value }) => {
1819
+ if (!getEffectiveIsWeb()) return null;
1820
+ if (value === "full") return { gridColumn: "1 / -1" };
1821
+ const n = parseInt(value, 10);
1822
+ return isNaN(n) ? null : { gridColumn: `span ${n} / span ${n}` };
1966
1823
  },
1967
- // ── Tint color (native-only, for Image and icon components) ──────────────
1968
- tint: ({ value, isArbitrary }, { colors }) => {
1969
- if (getEffectiveIsWeb()) return null;
1970
- const color = resolveColor(value, colors, isArbitrary);
1971
- return color ? { tintColor: color } : null;
1824
+ "col-start": ({ value, isArbitrary }) => {
1825
+ if (!getEffectiveIsWeb()) return null;
1826
+ if (value === "auto") return { gridColumnStart: "auto" };
1827
+ if (isArbitrary) return { gridColumnStart: value };
1828
+ const n = parseInt(value, 10);
1829
+ return isNaN(n) ? null : { gridColumnStart: n };
1972
1830
  },
1973
- // ── Size shorthand (sets width AND height in one utility) ────────────────
1974
- size: ({ value, isArbitrary }, { spacing }) => {
1975
- const v = resolveSizing(value, spacing, isArbitrary);
1976
- return v !== null ? { width: v, height: v } : null;
1831
+ "col-end": ({ value, isArbitrary }) => {
1832
+ if (!getEffectiveIsWeb()) return null;
1833
+ if (value === "auto") return { gridColumnEnd: "auto" };
1834
+ if (isArbitrary) return { gridColumnEnd: value };
1835
+ const n = parseInt(value, 10);
1836
+ return isNaN(n) ? null : { gridColumnEnd: n };
1977
1837
  },
1978
- // ── Animations (CSS keyframe animations, web-only) ────────────────────────
1979
- // Each variant injects a @keyframes rule once via the __keyframe marker.
1980
- // resolver.ts detects __keyframe, injects the rule, then strips the marker
1981
- // so it never reaches element inline styles.
1982
- animate: ({ value, isArbitrary }, theme) => {
1838
+ col: ({ value, isArbitrary }) => {
1983
1839
  if (!getEffectiveIsWeb()) return null;
1984
- if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
1985
- if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
1986
- const custom = theme.animation?.[value];
1987
- return custom ? buildAnimationValue(custom, theme.keyframes) : null;
1840
+ if (!value || value === "auto") return { gridColumn: "auto" };
1841
+ if (isArbitrary) return { gridColumn: value };
1842
+ return null;
1988
1843
  },
1989
- // ── Line-clamp (web-only) ─────────────────────────────────────────────────
1990
- "line-clamp": ({ value }) => {
1844
+ // ── Grid row placement (web-only) ─────────────────────────────────────────
1845
+ "row-span": ({ value }) => {
1991
1846
  if (!getEffectiveIsWeb()) return null;
1992
- if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
1847
+ if (value === "full") return { gridRow: "1 / -1" };
1993
1848
  const n = parseInt(value, 10);
1994
- if (isNaN(n) || n < 1) return null;
1995
- return {
1996
- overflow: "hidden",
1997
- display: "-webkit-box",
1998
- WebkitBoxOrient: "vertical",
1999
- WebkitLineClamp: n
1849
+ return isNaN(n) ? null : { gridRow: `span ${n} / span ${n}` };
1850
+ },
1851
+ "row-start": ({ value, isArbitrary }) => {
1852
+ if (!getEffectiveIsWeb()) return null;
1853
+ if (value === "auto") return { gridRowStart: "auto" };
1854
+ if (isArbitrary) return { gridRowStart: value };
1855
+ const n = parseInt(value, 10);
1856
+ return isNaN(n) ? null : { gridRowStart: n };
1857
+ },
1858
+ "row-end": ({ value, isArbitrary }) => {
1859
+ if (!getEffectiveIsWeb()) return null;
1860
+ if (value === "auto") return { gridRowEnd: "auto" };
1861
+ if (isArbitrary) return { gridRowEnd: value };
1862
+ const n = parseInt(value, 10);
1863
+ return isNaN(n) ? null : { gridRowEnd: n };
1864
+ },
1865
+ row: ({ value, isArbitrary }) => {
1866
+ if (!getEffectiveIsWeb()) return null;
1867
+ if (!value || value === "auto") return { gridRow: "auto" };
1868
+ if (isArbitrary) return { gridRow: value };
1869
+ return null;
1870
+ },
1871
+ // ── Grid alignment (web-only) ──────────────────────────────────────────────
1872
+ // place-items is shorthand for align-items + justify-items on a grid container
1873
+ "place-items": ({ value }) => {
1874
+ if (!getEffectiveIsWeb()) return null;
1875
+ const map = {
1876
+ start: "start",
1877
+ end: "end",
1878
+ center: "center",
1879
+ stretch: "stretch",
1880
+ baseline: "baseline"
2000
1881
  };
1882
+ return map[value] ? { placeItems: map[value] } : null;
2001
1883
  },
2002
- // ── Caret color (web-only) ────────────────────────────────────────────────
2003
- caret: ({ value, isArbitrary }, { colors }) => {
1884
+ // place-content is shorthand for align-content + justify-content on a grid container
1885
+ "place-content": ({ value }) => {
2004
1886
  if (!getEffectiveIsWeb()) return null;
2005
- if (value === "auto" || value === "transparent") return { caretColor: value };
2006
- const color = resolveColor(value, colors, isArbitrary);
2007
- return color ? { caretColor: color } : null;
1887
+ const map = {
1888
+ start: "start",
1889
+ end: "end",
1890
+ center: "center",
1891
+ stretch: "stretch",
1892
+ between: "space-between",
1893
+ around: "space-around",
1894
+ evenly: "space-evenly",
1895
+ baseline: "baseline"
1896
+ };
1897
+ return map[value] ? { placeContent: map[value] } : null;
2008
1898
  },
2009
- // ── Accent color (web-only) ───────────────────────────────────────────────
2010
- accent: ({ value, isArbitrary }, { colors }) => {
1899
+ // justify-items controls inline-axis alignment of grid items within their cells
1900
+ "justify-items": ({ value }) => {
2011
1901
  if (!getEffectiveIsWeb()) return null;
2012
- if (value === "auto") return { accentColor: "auto" };
2013
- const color = resolveColor(value, colors, isArbitrary);
2014
- return color ? { accentColor: color } : null;
1902
+ const map = {
1903
+ start: "start",
1904
+ end: "end",
1905
+ center: "center",
1906
+ stretch: "stretch"
1907
+ };
1908
+ return map[value] ? { justifyItems: map[value] } : null;
2015
1909
  },
2016
- // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2017
- // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2018
- // take stroke/fill as component PROPS, not style entries, so there's no
2019
- // reliable way to apply a resolved color through the `style` prop there.
2020
- // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2021
- // color-first-then-numeric-fallback disambiguation `border` uses above.
2022
- stroke: ({ value, isArbitrary }, { colors }) => {
1910
+ // place-self is shorthand for align-self + justify-self on a grid item
1911
+ "place-self": ({ value }) => {
2023
1912
  if (!getEffectiveIsWeb()) return null;
2024
- if (!value) return null;
2025
- if (value === "none") return { stroke: "none" };
2026
- if (isArbitrary) {
2027
- const w = toNativeValue(value);
2028
- return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2029
- }
2030
- const color = resolveColor(value, colors, false);
2031
- if (color) return { stroke: color };
2032
- const n = parseFloat(value);
2033
- return isNaN(n) ? null : { strokeWidth: n };
1913
+ const map = {
1914
+ auto: "auto",
1915
+ start: "start",
1916
+ end: "end",
1917
+ center: "center",
1918
+ stretch: "stretch"
1919
+ };
1920
+ return map[value] ? { placeSelf: map[value] } : null;
2034
1921
  },
2035
- fill: ({ value, isArbitrary }, { colors }) => {
1922
+ // justify-self controls inline-axis self-alignment of a grid item
1923
+ "justify-self": ({ value }) => {
2036
1924
  if (!getEffectiveIsWeb()) return null;
2037
- if (!value) return null;
2038
- if (value === "none") return { fill: "none" };
2039
- if (isArbitrary) return { fill: value };
2040
- const color = resolveColor(value, colors, false);
2041
- return color ? { fill: color } : null;
1925
+ const map = {
1926
+ auto: "auto",
1927
+ start: "start",
1928
+ end: "end",
1929
+ center: "center",
1930
+ stretch: "stretch"
1931
+ };
1932
+ return map[value] ? { justifySelf: map[value] } : null;
2042
1933
  },
2043
1934
  // ── Flex ───────────────────────────────────────────────────────────────────
1935
+ // flex-1/flex-auto/flex-none/grow/shrink are item-level sizing properties —
1936
+ // deliberately NOT implying display:flex here. See the "Flex grow / shrink"
1937
+ // comment in the standalone table above for why: these already work via the
1938
+ // PARENT's flex context regardless of this element's own display, so
1939
+ // forcing display:flex here would only be a surprising, unrelated side
1940
+ // effect on this element's OWN children.
2044
1941
  flex: ({ value, isArbitrary }, { flex }) => {
2045
1942
  if (!value) return { display: "flex" };
2046
1943
  if (isArbitrary) {
@@ -2059,10 +1956,6 @@ var RESOLVERS = {
2059
1956
  const n = parseFloat(value);
2060
1957
  return isNaN(n) ? null : { flex: n };
2061
1958
  },
2062
- basis: ({ value, isArbitrary }, { spacing }) => {
2063
- const v = resolveSizing(value, spacing, isArbitrary);
2064
- return v !== null ? { flexBasis: v } : null;
2065
- },
2066
1959
  grow: ({ value }, _) => {
2067
1960
  if (!value) return { flexGrow: 1 };
2068
1961
  const n = parseFloat(value);
@@ -2077,72 +1970,132 @@ var RESOLVERS = {
2077
1970
  const n = parseInt(value, 10);
2078
1971
  return isNaN(n) ? null : { order: negative ? -n : n };
2079
1972
  },
2080
- // ── Gap ────────────────────────────────────────────────────────────────────
2081
- gap: ({ value, negative, isArbitrary }, { spacing }) => {
2082
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2083
- return v !== null ? { gap: v } : null;
2084
- },
2085
- "gap-x": ({ value, negative, isArbitrary }, { spacing }) => {
2086
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2087
- return v !== null ? { columnGap: v } : null;
2088
- },
2089
- "gap-y": ({ value, negative, isArbitrary }, { spacing }) => {
2090
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2091
- return v !== null ? { rowGap: v } : null;
2092
- },
2093
- // ── Position ───────────────────────────────────────────────────────────────
2094
- top: ({ value, negative, isArbitrary }, { spacing }) => {
2095
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2096
- return v !== null ? { top: v } : null;
2097
- },
2098
- right: ({ value, negative, isArbitrary }, { spacing }) => {
2099
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2100
- return v !== null ? { right: v } : null;
2101
- },
2102
- bottom: ({ value, negative, isArbitrary }, { spacing }) => {
2103
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2104
- return v !== null ? { bottom: v } : null;
2105
- },
2106
- left: ({ value, negative, isArbitrary }, { spacing }) => {
2107
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2108
- return v !== null ? { left: v } : null;
2109
- },
2110
- inset: ({ value, negative, isArbitrary }, { spacing }) => {
2111
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2112
- return v !== null ? { top: v, right: v, bottom: v, left: v } : null;
2113
- },
2114
- "inset-x": ({ value, negative, isArbitrary }, { spacing }) => {
2115
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2116
- return v !== null ? { left: v, right: v } : null;
2117
- },
2118
- "inset-y": ({ value, negative, isArbitrary }, { spacing }) => {
2119
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2120
- return v !== null ? { top: v, bottom: v } : null;
2121
- },
2122
1973
  // ── Z-index ────────────────────────────────────────────────────────────────
2123
1974
  z: ({ value, isArbitrary }, { zIndex }) => {
2124
1975
  if (isArbitrary) {
2125
- const n2 = parseInt(value);
2126
- return isNaN(n2) ? null : { zIndex: n2 };
2127
- }
2128
- const v = zIndex[value];
2129
- if (v !== void 0) {
2130
- if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
2131
- return { zIndex: v };
1976
+ const n2 = parseInt(value);
1977
+ return isNaN(n2) ? null : { zIndex: n2 };
1978
+ }
1979
+ const v = zIndex[value];
1980
+ if (v !== void 0) {
1981
+ if (v === "auto") return getEffectiveIsWeb() ? { zIndex: "auto" } : null;
1982
+ return { zIndex: v };
1983
+ }
1984
+ const n = parseInt(value);
1985
+ return isNaN(n) ? null : { zIndex: n };
1986
+ },
1987
+ // ── Aspect ratio ───────────────────────────────────────────────────────────
1988
+ aspect: ({ value, isArbitrary }) => {
1989
+ if (isArbitrary) return { aspectRatio: value };
1990
+ const presets = { auto: "auto", square: 1, video: 16 / 9 };
1991
+ if (!(value in presets)) return null;
1992
+ const v = presets[value];
1993
+ if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
1994
+ return { aspectRatio: v };
1995
+ },
1996
+ // ── Columns (web-only) ─────────────────────────────────────────────────────
1997
+ columns: ({ value, isArbitrary }) => {
1998
+ if (!getEffectiveIsWeb()) return null;
1999
+ if (isArbitrary) return { columnCount: value };
2000
+ if (value === "auto") return { columnCount: "auto" };
2001
+ const n = parseInt(value, 10);
2002
+ if (!isNaN(n)) return { columnCount: n };
2003
+ const widths = {
2004
+ "3xs": "16rem",
2005
+ "2xs": "18rem",
2006
+ xs: "20rem",
2007
+ sm: "24rem",
2008
+ md: "28rem",
2009
+ lg: "32rem",
2010
+ xl: "36rem",
2011
+ "2xl": "42rem",
2012
+ "3xl": "48rem",
2013
+ "4xl": "56rem",
2014
+ "5xl": "64rem",
2015
+ "6xl": "72rem",
2016
+ "7xl": "80rem"
2017
+ };
2018
+ return widths[value] ? { columnWidth: widths[value] } : null;
2019
+ }
2020
+ };
2021
+
2022
+ // src/core/resolvers/typography.ts
2023
+ function resolveFontSize(value, fontSizes, isArbitrary) {
2024
+ if (isArbitrary) return getEffectiveIsWeb() ? value : toNativeValue(value);
2025
+ return fontSizes[value] ?? null;
2026
+ }
2027
+ var typographyResolvers = {
2028
+ // ── Text ───────────────────────────────────────────────────────────────────
2029
+ text: ({ value, isArbitrary }, { colors, fontSize }) => {
2030
+ const size = resolveFontSize(value, fontSize, false);
2031
+ if (!isArbitrary && size !== null) return { fontSize: size };
2032
+ if (isArbitrary) {
2033
+ if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
2034
+ return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
2035
+ }
2036
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
2132
2037
  }
2133
- const n = parseInt(value);
2134
- return isNaN(n) ? null : { zIndex: n };
2038
+ const color = resolveColor(value, colors, false);
2039
+ if (!color) return null;
2040
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
2135
2041
  },
2136
- // ── Opacity ────────────────────────────────────────────────────────────────
2137
- opacity: ({ value, isArbitrary }, { opacity }) => {
2042
+ "text-opacity": ({ value, isArbitrary }, _) => {
2043
+ if (!getEffectiveIsWeb()) return null;
2044
+ const n = parseFloat(value);
2045
+ if (isNaN(n)) return null;
2046
+ const v = isArbitrary ? n > 1 ? n / 100 : n : n / 100;
2047
+ return { "--text-opacity": v };
2048
+ },
2049
+ // ── Text decoration advanced (web-only) ───────────────────────────────────
2050
+ decoration: ({ value, isArbitrary }, { colors }) => {
2051
+ if (!getEffectiveIsWeb()) return null;
2138
2052
  if (isArbitrary) {
2139
- const v2 = parseFloat(value);
2140
- return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2053
+ if (/^\d/.test(value) || value.startsWith("calc(")) return { textDecorationThickness: value };
2054
+ return { textDecorationColor: value };
2141
2055
  }
2142
- const v = opacity[value];
2143
- if (v !== void 0) return { opacity: v };
2144
- const n = parseFloat(value);
2145
- return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2056
+ const thickTokens = {
2057
+ auto: "auto",
2058
+ "from-font": "from-font",
2059
+ "0": "0px",
2060
+ "1": "1px",
2061
+ "2": "2px",
2062
+ "4": "4px",
2063
+ "8": "8px"
2064
+ };
2065
+ if (value in thickTokens) return { textDecorationThickness: thickTokens[value] };
2066
+ const styleTokens = {
2067
+ solid: "solid",
2068
+ dashed: "dashed",
2069
+ dotted: "dotted",
2070
+ double: "double",
2071
+ wavy: "wavy"
2072
+ };
2073
+ if (value in styleTokens) return { textDecorationStyle: styleTokens[value] };
2074
+ const color = resolveColor(value, colors, false);
2075
+ return color ? { textDecorationColor: color } : null;
2076
+ },
2077
+ "underline-offset": ({ value, isArbitrary }) => {
2078
+ if (!getEffectiveIsWeb()) return null;
2079
+ if (isArbitrary) return { textUnderlineOffset: value };
2080
+ const offsets = {
2081
+ auto: "auto",
2082
+ "0": "0px",
2083
+ "1": "1px",
2084
+ "2": "2px",
2085
+ "4": "4px",
2086
+ "8": "8px"
2087
+ };
2088
+ return offsets[value] ? { textUnderlineOffset: offsets[value] } : null;
2089
+ },
2090
+ // ── Content (web-only — for before:/after: pseudo-elements, which don't exist on native) ──
2091
+ // Arbitrary value carries its own quotes from the bracket syntax (content-['*'] parses to
2092
+ // the literal string 'you can see it' quotes included), so it passes straight through as a
2093
+ // valid `content: '*'` CSS value with no extra wrapping needed.
2094
+ content: ({ value, isArbitrary }) => {
2095
+ if (!getEffectiveIsWeb()) return null;
2096
+ if (isArbitrary) return { content: value };
2097
+ const presets = { none: "none" };
2098
+ return value in presets ? { content: presets[value] } : null;
2146
2099
  },
2147
2100
  // ── Font ───────────────────────────────────────────────────────────────────
2148
2101
  font: ({ value, isArbitrary }, { fontFamily, fontWeight }) => {
@@ -2168,10 +2121,57 @@ var RESOLVERS = {
2168
2121
  const v = letterSpacing[value];
2169
2122
  return v !== void 0 ? { letterSpacing: v } : null;
2170
2123
  },
2171
- // ── Shadow ─────────────────────────────────────────────────────────────────
2172
- shadow: ({ value }, { shadow }) => {
2173
- const key = value === "" ? "DEFAULT" : value;
2174
- return shadow[key] ?? null;
2124
+ // ── Line-clamp (web-only) ─────────────────────────────────────────────────
2125
+ "line-clamp": ({ value }) => {
2126
+ if (!getEffectiveIsWeb()) return null;
2127
+ if (value === "none") return { overflow: "visible", display: "block", WebkitLineClamp: "unset" };
2128
+ const n = parseInt(value, 10);
2129
+ if (isNaN(n) || n < 1) return null;
2130
+ return {
2131
+ overflow: "hidden",
2132
+ display: "-webkit-box",
2133
+ WebkitBoxOrient: "vertical",
2134
+ WebkitLineClamp: n
2135
+ };
2136
+ }
2137
+ };
2138
+
2139
+ // src/core/resolvers/transform.ts
2140
+ var transformResolvers = {
2141
+ // ── Transform extras (web-only) ───────────────────────────────────────────
2142
+ "skew-x": ({ value, negative, isArbitrary }) => {
2143
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2144
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2145
+ return getEffectiveIsWeb() ? { transform: `skewX(${deg})` } : { transform: [{ skewX: deg }] };
2146
+ },
2147
+ "skew-y": ({ value, negative, isArbitrary }) => {
2148
+ const deg = isArbitrary ? value : `${(negative ? -1 : 1) * parseFloat(value)}deg`;
2149
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2150
+ return getEffectiveIsWeb() ? { transform: `skewY(${deg})` } : { transform: [{ skewY: deg }] };
2151
+ },
2152
+ // Arbitrary full transform string: transform-[rotate(45deg)_scale(1.5)]
2153
+ transform: ({ value, isArbitrary }) => {
2154
+ if (!getEffectiveIsWeb()) return null;
2155
+ if (isArbitrary) return { transform: value.replace(/_/g, " ") };
2156
+ if (value === "none") return { transform: "none" };
2157
+ return null;
2158
+ },
2159
+ // Transform origin
2160
+ origin: ({ value, isArbitrary }) => {
2161
+ if (!getEffectiveIsWeb()) return null;
2162
+ if (isArbitrary) return { transformOrigin: value.replace(/_/g, " ") };
2163
+ const origins = {
2164
+ center: "center",
2165
+ top: "top",
2166
+ "top-right": "top right",
2167
+ right: "right",
2168
+ "bottom-right": "bottom right",
2169
+ bottom: "bottom",
2170
+ "bottom-left": "bottom left",
2171
+ left: "left",
2172
+ "top-left": "top left"
2173
+ };
2174
+ return origins[value] ? { transformOrigin: origins[value] } : null;
2175
2175
  },
2176
2176
  // ── Scale ──────────────────────────────────────────────────────────────────
2177
2177
  // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
@@ -2202,35 +2202,86 @@ var RESOLVERS = {
2202
2202
  const finalDeg = negative ? -deg : deg;
2203
2203
  return getEffectiveIsWeb() ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
2204
2204
  },
2205
- // ── Translate ──────────────────────────────────────────────────────────────
2206
- "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
2207
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2208
- if (v === null) return null;
2209
- if (getEffectiveIsWeb()) return { transform: `translateX(${typeof v === "number" ? `${v}px` : v})` };
2210
- if (typeof v === "string") {
2211
- const n = parseFloat(v);
2212
- return isNaN(n) ? null : { transform: [{ translateX: n }] };
2205
+ // ── Perspective transform (cross-platform: iOS, Android, web) ────────────
2206
+ perspective: ({ value, isArbitrary }) => {
2207
+ if (isArbitrary) {
2208
+ const numPx = parseFloat(value);
2209
+ if (getEffectiveIsWeb()) return { transform: `perspective(${value})` };
2210
+ return isNaN(numPx) ? null : { transform: [{ perspective: numPx }] };
2213
2211
  }
2214
- return { transform: [{ translateX: v }] };
2212
+ if (value === "none") return getEffectiveIsWeb() ? { transform: "none" } : null;
2213
+ const n = parseFloat(value);
2214
+ if (isNaN(n)) return null;
2215
+ return getEffectiveIsWeb() ? { transform: `perspective(${n}px)` } : { transform: [{ perspective: n }] };
2216
+ }
2217
+ };
2218
+
2219
+ // src/core/resolvers/effects.ts
2220
+ function keyframeDeclToCSS(decl) {
2221
+ return Object.entries(decl).map(([prop, val]) => `${prop.replace(/([A-Z])/g, "-$1").toLowerCase()}: ${val}`).join("; ");
2222
+ }
2223
+ function buildKeyframeCSS(name, steps) {
2224
+ const body = Object.entries(steps).map(([selector, decl]) => `${selector} { ${keyframeDeclToCSS(decl)} }`).join(" ");
2225
+ return `@keyframes ${name} { ${body} }`;
2226
+ }
2227
+ var BUILTIN_ANIMATIONS = {
2228
+ none: { animation: "none" },
2229
+ spin: {
2230
+ animation: "kb-spin 1s linear infinite",
2231
+ __keyframe: "@keyframes kb-spin { from { transform: rotate(0deg) } to { transform: rotate(360deg) } }"
2215
2232
  },
2216
- "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
2217
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2218
- if (v === null) return null;
2219
- if (getEffectiveIsWeb()) return { transform: `translateY(${typeof v === "number" ? `${v}px` : v})` };
2220
- if (typeof v === "string") {
2221
- const n = parseFloat(v);
2222
- return isNaN(n) ? null : { transform: [{ translateY: n }] };
2233
+ ping: {
2234
+ animation: "kb-ping 1s cubic-bezier(0,0,0.2,1) infinite",
2235
+ __keyframe: "@keyframes kb-ping { 75%, 100% { transform: scale(2); opacity: 0 } }"
2236
+ },
2237
+ pulse: {
2238
+ animation: "kb-pulse 2s cubic-bezier(0.4,0,0.6,1) infinite",
2239
+ __keyframe: "@keyframes kb-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }"
2240
+ },
2241
+ bounce: {
2242
+ animation: "kb-bounce 1s infinite",
2243
+ __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) } }"
2244
+ }
2245
+ };
2246
+ function buildAnimationValue(animation, keyframes) {
2247
+ const words = animation.trim().split(/\s+/);
2248
+ const name = words[0];
2249
+ if (!name) return { animation };
2250
+ const builtin = BUILTIN_ANIMATIONS[name];
2251
+ if (builtin?.__keyframe) {
2252
+ const kbName = builtin.animation.split(/\s+/, 1)[0];
2253
+ return { animation: [kbName, ...words.slice(1)].join(" "), __keyframe: builtin.__keyframe };
2254
+ }
2255
+ const steps = keyframes?.[name];
2256
+ return steps ? { animation, __keyframe: buildKeyframeCSS(name, steps) } : { animation };
2257
+ }
2258
+ var effectResolvers = {
2259
+ // ── Opacity ────────────────────────────────────────────────────────────────
2260
+ opacity: ({ value, isArbitrary }, { opacity }) => {
2261
+ if (isArbitrary) {
2262
+ const v2 = parseFloat(value);
2263
+ return isNaN(v2) ? null : { opacity: v2 > 1 ? v2 / 100 : v2 };
2223
2264
  }
2224
- return { transform: [{ translateY: v }] };
2265
+ const v = opacity[value];
2266
+ if (v !== void 0) return { opacity: v };
2267
+ const n = parseFloat(value);
2268
+ return isNaN(n) ? null : { opacity: n > 1 ? n / 100 : n };
2225
2269
  },
2226
- // ── Aspect ratio ───────────────────────────────────────────────────────────
2227
- aspect: ({ value, isArbitrary }) => {
2228
- if (isArbitrary) return { aspectRatio: value };
2229
- const presets = { auto: "auto", square: 1, video: 16 / 9 };
2230
- if (!(value in presets)) return null;
2231
- const v = presets[value];
2232
- if (v === "auto") return getEffectiveIsWeb() ? { aspectRatio: "auto" } : null;
2233
- return { aspectRatio: v };
2270
+ // ── Shadow ─────────────────────────────────────────────────────────────────
2271
+ shadow: ({ value }, { shadow }) => {
2272
+ const key = value === "" ? "DEFAULT" : value;
2273
+ return shadow[key] ?? null;
2274
+ },
2275
+ // ── Animations (CSS keyframe animations, web-only) ────────────────────────
2276
+ // Each variant injects a @keyframes rule once via the __keyframe marker.
2277
+ // resolver.ts detects __keyframe, injects the rule, then strips the marker
2278
+ // so it never reaches element inline styles.
2279
+ animate: ({ value, isArbitrary }, theme) => {
2280
+ if (!getEffectiveIsWeb()) return null;
2281
+ if (isArbitrary) return buildAnimationValue(value.replace(/_/g, " "), theme.keyframes);
2282
+ if (value in BUILTIN_ANIMATIONS) return BUILTIN_ANIMATIONS[value];
2283
+ const custom = theme.animation?.[value];
2284
+ return custom ? buildAnimationValue(custom, theme.keyframes) : null;
2234
2285
  },
2235
2286
  // ── Transition (web-only; use Animated API on native) ─────────────────────
2236
2287
  transition: ({ value }) => {
@@ -2258,91 +2309,107 @@ var RESOLVERS = {
2258
2309
  if (!isArbitrary && isNaN(parseFloat(value))) return null;
2259
2310
  const ms = isArbitrary ? value : `${value}ms`;
2260
2311
  return { transitionDelay: ms };
2312
+ }
2313
+ };
2314
+
2315
+ // src/core/resolvers/misc.ts
2316
+ var miscResolvers = {
2317
+ // ── Arbitrary CSS property (web-only): [property:value] with no utility prefix ──
2318
+ // e.g. [mask-type:luminance], [--my-var:10px]. parser.ts already isolates this case
2319
+ // as utility: '' — it's ALSO how a stray, prefix-less negative bracket ("-[10px]",
2320
+ // presumably a typo) parses, which is why this only fires when the arbitrary value
2321
+ // itself contains a "property:value" pair; anything else (no colon) still resolves
2322
+ // to null exactly as before this resolver existed, rather than guessing a property.
2323
+ //
2324
+ // Only the FIRST colon splits property from value — the value itself may contain
2325
+ // more colons (a URL's "http://…") and must stay intact: [background:url(http://x/a.png)].
2326
+ // camelToKebab() in resolver.ts is a no-op on an already-kebab-case (or "--"-prefixed
2327
+ // custom property) key, so it can be used as the style object key as-written with no
2328
+ // extra case conversion — it only needs to look like a plausible CSS property name.
2329
+ "": ({ value, isArbitrary, negative }) => {
2330
+ if (!getEffectiveIsWeb() || !isArbitrary || !value) return null;
2331
+ if (negative) return null;
2332
+ const colonIdx = value.indexOf(":");
2333
+ if (colonIdx <= 0) return null;
2334
+ const prop = value.slice(0, colonIdx).trim();
2335
+ const cssValue = value.slice(colonIdx + 1).trim();
2336
+ if (!cssValue || !/^(--[\w-]+|[a-zA-Z-]+)$/.test(prop)) return null;
2337
+ return { [prop]: cssValue };
2261
2338
  },
2262
- // ── Ring ───────────────────────────────────────────────────────────────────
2263
- // Web: box-shadow outline ring (doesn't affect layout).
2264
- // Native: React Native has no box-shadow, so this falls back to borderWidth/
2265
- // borderColor — the closest visual approximation (used by other RN Tailwind-
2266
- // likes for the same reason). Unlike the web ring, this DOES affect layout,
2267
- // and it shares its properties with the `border` utility — combining
2268
- // `border-*` and `ring-*` on the same native element means whichever class
2269
- // comes later wins, since both ultimately set borderWidth/borderColor.
2270
- ring: ({ value, isArbitrary }, { colors }) => {
2271
- const onWeb = getEffectiveIsWeb();
2272
- const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
2273
- const DEFAULT_WIDTH = 3;
2274
- const asNative = (w, color2) => w === 0 ? { borderWidth: 0 } : { borderWidth: w, borderColor: color2 };
2275
- if (!value) {
2276
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2339
+ // ── SVG stroke/fill (web-only) ────────────────────────────────────────────
2340
+ // Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
2341
+ // take stroke/fill as component PROPS, not style entries, so there's no
2342
+ // reliable way to apply a resolved color through the `style` prop there.
2343
+ // `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
2344
+ // color-first-then-numeric-fallback disambiguation `border` uses above.
2345
+ stroke: ({ value, isArbitrary }, { colors }) => {
2346
+ if (!getEffectiveIsWeb()) return null;
2347
+ if (!value) return null;
2348
+ if (value === "none") return { stroke: "none" };
2349
+ if (isArbitrary) {
2350
+ const w = toNativeValue(value);
2351
+ return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
2277
2352
  }
2278
- if (value === "inset") {
2279
- return onWeb ? { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` } : asNative(DEFAULT_WIDTH, DEFAULT_COLOR);
2353
+ const color = resolveColor(value, colors, false);
2354
+ if (color) return { stroke: color };
2355
+ const n = parseFloat(value);
2356
+ return isNaN(n) ? null : { strokeWidth: n };
2357
+ },
2358
+ fill: ({ value, isArbitrary }, { colors }) => {
2359
+ if (!getEffectiveIsWeb()) return null;
2360
+ if (!value) return null;
2361
+ if (value === "none") return { fill: "none" };
2362
+ if (isArbitrary) return { fill: value };
2363
+ const color = resolveColor(value, colors, false);
2364
+ return color ? { fill: color } : null;
2365
+ },
2366
+ // ── Text shadow (cross-platform: iOS, Android, web) ─────────────────────
2367
+ // Named presets give cross-platform shadow; arbitrary is web-only.
2368
+ "text-shadow": ({ value, isArbitrary }, { colors }) => {
2369
+ if (value === "none") {
2370
+ return getEffectiveIsWeb() ? { textShadow: "none" } : { textShadowColor: "transparent", textShadowOffset: { width: 0, height: 0 }, textShadowRadius: 0 };
2280
2371
  }
2281
- const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2282
- if (!isArbitrary && value in widthTokens) {
2283
- const w = widthTokens[value];
2284
- return onWeb ? { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` } : asNative(w, DEFAULT_COLOR);
2372
+ const webPresets = {
2373
+ "": "0 2px 4px rgba(0,0,0,0.3)",
2374
+ sm: "0 1px 2px rgba(0,0,0,0.3)",
2375
+ md: "0 4px 6px rgba(0,0,0,0.3)",
2376
+ lg: "0 8px 16px rgba(0,0,0,0.5)",
2377
+ xl: "0 16px 32px rgba(0,0,0,0.5)"
2378
+ };
2379
+ const nativePresets = {
2380
+ "": { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 2 }, textShadowRadius: 4 },
2381
+ sm: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 },
2382
+ md: { textShadowColor: "rgba(0,0,0,0.3)", textShadowOffset: { width: 0, height: 4 }, textShadowRadius: 6 },
2383
+ lg: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 8 }, textShadowRadius: 16 },
2384
+ xl: { textShadowColor: "rgba(0,0,0,0.5)", textShadowOffset: { width: 0, height: 16 }, textShadowRadius: 32 }
2385
+ };
2386
+ if (value in webPresets) {
2387
+ if (getEffectiveIsWeb()) return { textShadow: webPresets[value] };
2388
+ return nativePresets[value] ?? null;
2285
2389
  }
2286
2390
  if (isArbitrary) {
2287
- const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
2288
- if (numMatch) {
2289
- const unit = numMatch[2] ?? "px";
2290
- if (onWeb) return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
2291
- return unit === "px" ? asNative(parseFloat(numMatch[1]), DEFAULT_COLOR) : null;
2292
- }
2293
- return onWeb ? { boxShadow: value.replace(/_/g, " ") } : null;
2391
+ return getEffectiveIsWeb() ? { textShadow: value.replace(/_/g, " ") } : null;
2294
2392
  }
2295
2393
  const color = resolveColor(value, colors, false);
2296
2394
  if (color) {
2297
- return onWeb ? { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` } : asNative(DEFAULT_WIDTH, color);
2395
+ return getEffectiveIsWeb() ? { textShadow: `0 2px 4px ${color}` } : { textShadowColor: color };
2298
2396
  }
2299
2397
  return null;
2300
- },
2301
- // Web-only, unlike `ring` above: this stacks a second box-shadow layer to
2302
- // create a gap between the element and the ring. There's no native
2303
- // equivalent to approximate that with (a border can't create a gap outside
2304
- // its own element without an extra wrapper view), so this stays a no-op
2305
- // on native rather than rendering a misleading half-translation.
2306
- "ring-offset": ({ value, isArbitrary }, _theme) => {
2307
- if (!getEffectiveIsWeb()) return null;
2308
- const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
2309
- const DEFAULT_RING_WIDTH = 3;
2310
- const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
2311
- let offsetWidth;
2312
- if (!isArbitrary) {
2313
- offsetWidth = value in offsetTokens ? offsetTokens[value] : null;
2314
- } else {
2315
- offsetWidth = /^-?\d+(\.\d+)?$/.test(value) ? parseFloat(value) : value;
2316
- }
2317
- if (offsetWidth === null || typeof offsetWidth === "number" && isNaN(offsetWidth)) return null;
2318
- const isZero = offsetWidth === 0 || offsetWidth === "0";
2319
- const offsetCss = typeof offsetWidth === "number" ? `${offsetWidth}px` : offsetWidth;
2320
- return {
2321
- 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}`
2322
- };
2323
- },
2324
- // ── Space between ─────────────────────────────────────────────────────────
2325
- // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2326
- // On native (RN 0.71+): uses columnGap/rowGap directly.
2327
- "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2328
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2329
- if (v === null) return null;
2330
- if (getEffectiveIsWeb()) return { __spaceX: v };
2331
- return { columnGap: v };
2332
- },
2333
- "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2334
- const v = resolveSpacing(value, negative, spacing, isArbitrary);
2335
- if (v === null) return null;
2336
- if (getEffectiveIsWeb()) return { __spaceY: v };
2337
- return { rowGap: v };
2338
2398
  }
2339
2399
  };
2340
- for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
2341
- RESOLVERS[utility] = makeSpacingResolver(prop);
2342
- }
2343
- for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
2344
- RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
2345
- }
2400
+
2401
+ // src/core/utilities.ts
2402
+ var RESOLVERS = {
2403
+ ...colorResolvers,
2404
+ ...spacingResolvers,
2405
+ ...borderResolvers,
2406
+ ...layoutResolvers,
2407
+ ...typographyResolvers,
2408
+ ...filterResolvers,
2409
+ ...transformResolvers,
2410
+ ...effectResolvers,
2411
+ ...miscResolvers
2412
+ };
2346
2413
  var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
2347
2414
  function resolveUtility(parsed, theme) {
2348
2415
  if (!parsed.value) {
@@ -2390,6 +2457,47 @@ function kbachWarn(message) {
2390
2457
  );
2391
2458
  }
2392
2459
 
2460
+ // src/core/cache.ts
2461
+ var LRUCache = class {
2462
+ constructor(capacity = 1e4, onEvict) {
2463
+ this.capacity = capacity;
2464
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2465
+ this.cache = /* @__PURE__ */ new Map();
2466
+ this.onEvict = onEvict;
2467
+ }
2468
+ get(key) {
2469
+ if (!this.cache.has(key)) return void 0;
2470
+ const value = this.cache.get(key);
2471
+ this.cache.delete(key);
2472
+ this.cache.set(key, value);
2473
+ return value;
2474
+ }
2475
+ set(key, value) {
2476
+ if (this.cache.has(key)) {
2477
+ this.cache.delete(key);
2478
+ } else if (this.cache.size >= this.capacity) {
2479
+ const oldestKey = this.cache.keys().next().value;
2480
+ const oldestValue = this.cache.get(oldestKey);
2481
+ this.cache.delete(oldestKey);
2482
+ this.onEvict?.(oldestKey, oldestValue);
2483
+ }
2484
+ this.cache.set(key, value);
2485
+ return this;
2486
+ }
2487
+ has(key) {
2488
+ return this.cache.has(key);
2489
+ }
2490
+ delete(key) {
2491
+ return this.cache.delete(key);
2492
+ }
2493
+ clear() {
2494
+ this.cache.clear();
2495
+ }
2496
+ get size() {
2497
+ return this.cache.size;
2498
+ }
2499
+ };
2500
+
2393
2501
  // src/core/registry.ts
2394
2502
  var BUILTIN_MODIFIERS = {
2395
2503
  // ── Mode ─────────────────────────────────────────────────────────────────
@@ -2476,7 +2584,7 @@ function clearPluginModifiers() {
2476
2584
  _invalidate();
2477
2585
  }
2478
2586
  var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
2479
- var _namedModifierCache = /* @__PURE__ */ new Map();
2587
+ var _namedModifierCache = new LRUCache(1e4);
2480
2588
  function getNamedGroupPeerModifier(name) {
2481
2589
  const cached = _namedModifierCache.get(name);
2482
2590
  if (cached) return cached;
@@ -2647,51 +2755,10 @@ function findOuterColon(s) {
2647
2755
  return -1;
2648
2756
  }
2649
2757
 
2650
- // src/core/cache.ts
2651
- var LRUCache = class {
2652
- constructor(capacity = 1e4, onEvict) {
2653
- this.capacity = capacity;
2654
- if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2655
- this.cache = /* @__PURE__ */ new Map();
2656
- this.onEvict = onEvict;
2657
- }
2658
- get(key) {
2659
- if (!this.cache.has(key)) return void 0;
2660
- const value = this.cache.get(key);
2661
- this.cache.delete(key);
2662
- this.cache.set(key, value);
2663
- return value;
2664
- }
2665
- set(key, value) {
2666
- if (this.cache.has(key)) {
2667
- this.cache.delete(key);
2668
- } else if (this.cache.size >= this.capacity) {
2669
- const oldestKey = this.cache.keys().next().value;
2670
- const oldestValue = this.cache.get(oldestKey);
2671
- this.cache.delete(oldestKey);
2672
- this.onEvict?.(oldestKey, oldestValue);
2673
- }
2674
- this.cache.set(key, value);
2675
- return this;
2676
- }
2677
- has(key) {
2678
- return this.cache.has(key);
2679
- }
2680
- delete(key) {
2681
- return this.cache.delete(key);
2682
- }
2683
- clear() {
2684
- this.cache.clear();
2685
- }
2686
- get size() {
2687
- return this.cache.size;
2688
- }
2689
- };
2690
-
2691
2758
  // src/core/resolver.ts
2692
- var FONT_KEY = "__kbach_default_font__";
2759
+ var _defaultFontFamily;
2693
2760
  function setDefaultFontFamily(font) {
2694
- globalThis[FONT_KEY] = font;
2761
+ _defaultFontFamily = font;
2695
2762
  }
2696
2763
  var _styleEl = null;
2697
2764
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
@@ -2711,9 +2778,9 @@ function evictInjectedRule(rule) {
2711
2778
  }
2712
2779
  }
2713
2780
  var _injectedRules = new LRUCache(5e4, evictInjectedRule);
2714
- var _CSS_DISABLED_KEY = "__kbach_runtime_css_disabled__";
2781
+ var _runtimeCSSDisabled = false;
2715
2782
  function isRuntimeCSSDisabled() {
2716
- return !!globalThis[_CSS_DISABLED_KEY];
2783
+ return _runtimeCSSDisabled;
2717
2784
  }
2718
2785
  function getStyleEl() {
2719
2786
  if (_styleEl) return _styleEl;
@@ -2936,14 +3003,7 @@ function deepMerge(base, override) {
2936
3003
  }
2937
3004
  return result;
2938
3005
  }
2939
- var CONFIG_KEY = "__kbach_config_store__";
2940
- function getConfigStore() {
2941
- const g = globalThis;
2942
- if (!g[CONFIG_KEY]) {
2943
- g[CONFIG_KEY] = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
2944
- }
2945
- return g[CONFIG_KEY];
2946
- }
3006
+ var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
2947
3007
  function resolveOneRef(ref, colors) {
2948
3008
  const lastDash = ref.lastIndexOf("-");
2949
3009
  if (lastDash <= 0) return null;
@@ -3017,8 +3077,7 @@ function buildConfig(userConfig) {
3017
3077
  clearPluginUtilities();
3018
3078
  clearPluginModifiers();
3019
3079
  }
3020
- const _store = getConfigStore();
3021
- for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
3080
+ for (const k of Object.keys(configStore.customVariants)) delete configStore.customVariants[k];
3022
3081
  const pluginAPI = makePluginAPI(resolved.theme);
3023
3082
  for (const plugin of resolved.plugins) {
3024
3083
  plugin(pluginAPI);
@@ -3065,26 +3124,7 @@ function makePluginAPI(theme) {
3065
3124
  }
3066
3125
  };
3067
3126
  }
3068
- var customVariants = new Proxy(
3069
- {},
3070
- {
3071
- get(_t, p) {
3072
- return typeof p === "string" ? getConfigStore().customVariants[p] : void 0;
3073
- },
3074
- set(_t, p, v) {
3075
- if (typeof p === "string") getConfigStore().customVariants[p] = v;
3076
- return true;
3077
- },
3078
- ownKeys() {
3079
- return Object.keys(getConfigStore().customVariants);
3080
- },
3081
- getOwnPropertyDescriptor(_t, p) {
3082
- if (typeof p !== "string") return void 0;
3083
- const v = getConfigStore().customVariants[p];
3084
- return v !== void 0 ? { value: v, writable: true, enumerable: true, configurable: true } : void 0;
3085
- }
3086
- }
3087
- );
3127
+ var customVariants = configStore.customVariants;
3088
3128
 
3089
3129
  // src/vite-plugin.ts
3090
3130
  var process2 = import_node_process.default;
@@ -3444,7 +3484,9 @@ function pushTokens(str, into) {
3444
3484
  if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
3445
3485
  }
3446
3486
  }
3447
- function scanDir(dir, onFile) {
3487
+ var MAX_SCAN_DEPTH = 10;
3488
+ function scanDir(dir, onFile, depth = 0) {
3489
+ if (depth > MAX_SCAN_DEPTH) return;
3448
3490
  let entries;
3449
3491
  try {
3450
3492
  entries = (0, import_fs.readdirSync)(dir);
@@ -3459,7 +3501,7 @@ function scanDir(dir, onFile) {
3459
3501
  const full = (0, import_path.join)(dir, entry);
3460
3502
  try {
3461
3503
  const st = (0, import_fs.statSync)(full);
3462
- if (st.isDirectory()) scanDir(full, onFile);
3504
+ if (st.isDirectory()) scanDir(full, onFile, depth + 1);
3463
3505
  else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
3464
3506
  } catch (err) {
3465
3507
  warn(`Can't process ${full}: ${err.message}`);
@@ -3477,7 +3519,8 @@ function scanCssFileInto(filePath, into) {
3477
3519
  } catch {
3478
3520
  }
3479
3521
  }
3480
- function scanCssSelectorsInto(dir, into) {
3522
+ function scanCssSelectorsInto(dir, into, depth = 0) {
3523
+ if (depth > MAX_SCAN_DEPTH) return;
3481
3524
  let entries;
3482
3525
  try {
3483
3526
  entries = (0, import_fs.readdirSync)(dir);
@@ -3489,7 +3532,7 @@ function scanCssSelectorsInto(dir, into) {
3489
3532
  const full = (0, import_path.join)(dir, entry);
3490
3533
  try {
3491
3534
  const st = (0, import_fs.statSync)(full);
3492
- if (st.isDirectory()) scanCssSelectorsInto(full, into);
3535
+ if (st.isDirectory()) scanCssSelectorsInto(full, into, depth + 1);
3493
3536
  else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
3494
3537
  } catch {
3495
3538
  }
@@ -3556,6 +3599,17 @@ function kbach(userConfigOrOptions) {
3556
3599
  function generateCSS() {
3557
3600
  return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
3558
3601
  }
3602
+ function syncMainCSSFile(server) {
3603
+ const active = /* @__PURE__ */ new Set();
3604
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3605
+ for (const tok of tokenCSS.keys()) {
3606
+ if (!active.has(tok)) tokenCSS.delete(tok);
3607
+ }
3608
+ if (mainCSSFile) {
3609
+ const changed = writeKbachToFile(mainCSSFile, generateCSS());
3610
+ if (changed) server.watcher.emit("change", mainCSSFile);
3611
+ }
3612
+ }
3559
3613
  return {
3560
3614
  name: "kbach",
3561
3615
  enforce: "pre",
@@ -3615,6 +3669,37 @@ ${code}`,
3615
3669
  initialScan();
3616
3670
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3617
3671
  },
3672
+ // Vite's `handleHotUpdate` hook only fires for `type === "update"` (plain
3673
+ // file edits) — it's never called for file create/delete, which land on
3674
+ // the watcher's own 'add'/'unlink' events instead (confirmed against
3675
+ // vite's own onHMRUpdate/handleHMRUpdate: the hook loop is gated on
3676
+ // `type === "update"`). Without also listening on those two events here,
3677
+ // creating a new file with new Kbach classes never adds them to
3678
+ // kbach.css, and deleting a file never removes its classes, until a full
3679
+ // dev-server restart. configureServer gives access to the raw watcher so
3680
+ // both cases can share the same sync logic as a plain edit.
3681
+ configureServer(server) {
3682
+ const onAddOrUnlink = (file, isUnlink) => {
3683
+ if (file.includes("node_modules")) return;
3684
+ if (CSS_FILE_RE.test(file)) {
3685
+ if (!isUnlink) scanCssFileInto(file, projectCssClasses);
3686
+ return;
3687
+ }
3688
+ if (!/\.(tsx?|jsx?)$/.test(file)) return;
3689
+ if (isUnlink) {
3690
+ fileTokens.delete(normPath(file));
3691
+ } else {
3692
+ try {
3693
+ processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
3694
+ } catch {
3695
+ fileTokens.delete(normPath(file));
3696
+ }
3697
+ }
3698
+ syncMainCSSFile(server);
3699
+ };
3700
+ server.watcher.on("add", (file) => onAddOrUnlink(file, false));
3701
+ server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
3702
+ },
3618
3703
  handleHotUpdate({ file, server }) {
3619
3704
  if (file.includes("node_modules")) return;
3620
3705
  if (CSS_FILE_RE.test(file)) {
@@ -3627,15 +3712,7 @@ ${code}`,
3627
3712
  } catch {
3628
3713
  fileTokens.delete(normPath(file));
3629
3714
  }
3630
- const active = /* @__PURE__ */ new Set();
3631
- for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3632
- for (const tok of tokenCSS.keys()) {
3633
- if (!active.has(tok)) tokenCSS.delete(tok);
3634
- }
3635
- if (mainCSSFile) {
3636
- const changed = writeKbachToFile(mainCSSFile, generateCSS());
3637
- if (changed) server.watcher.emit("change", mainCSSFile);
3638
- }
3715
+ syncMainCSSFile(server);
3639
3716
  }
3640
3717
  };
3641
3718
  }