@kbach/react 0.6.37 → 0.6.40

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.
@@ -619,6 +619,11 @@ var defaultTheme = {
619
619
  function isModeAwareColor(v) {
620
620
  return typeof v === "object" && v !== null && "light" in v && "dark" in v;
621
621
  }
622
+ function splitColorShadeRef(ref) {
623
+ const lastDash = ref.lastIndexOf("-");
624
+ if (lastDash <= 0) return null;
625
+ return { name: ref.slice(0, lastDash), shade: ref.slice(lastDash + 1) };
626
+ }
622
627
 
623
628
  // src/core/resolvers/color.ts
624
629
  function pickModeAwareFallback(v) {
@@ -638,13 +643,11 @@ function resolveColor(value, colors, isArbitrary) {
638
643
  else if (isModeAwareColor(entry)) hex = pickModeAwareFallback(entry);
639
644
  else if (typeof entry === "object" && "6" in entry) hex = pickModeAwareFallback(entry["6"]);
640
645
  } else {
641
- const lastDash = colorPart.lastIndexOf("-");
642
- if (lastDash > 0) {
643
- const colorName = colorPart.slice(0, lastDash);
644
- const shade = colorPart.slice(lastDash + 1);
645
- const scale = colors[colorName];
646
- if (scale && typeof scale === "object" && !isModeAwareColor(scale) && shade in scale) {
647
- const shadeVal = scale[shade];
646
+ const split = splitColorShadeRef(colorPart);
647
+ if (split) {
648
+ const scale = colors[split.name];
649
+ if (scale && typeof scale === "object" && !isModeAwareColor(scale) && split.shade in scale) {
650
+ const shadeVal = scale[split.shade];
648
651
  hex = shadeVal !== void 0 ? pickModeAwareFallback(shadeVal) : null;
649
652
  }
650
653
  }
@@ -785,6 +788,7 @@ function resolveSpacing(value, negative, spacing, isArbitrary) {
785
788
  if (negative) {
786
789
  if (typeof resolved === "number") return -resolved;
787
790
  if (typeof resolved === "string") {
791
+ if (resolved.startsWith("-")) return resolved.slice(1);
788
792
  if (/^\d/.test(resolved)) return `-${resolved}`;
789
793
  if (/^(calc|var|min|max|clamp|env)\s*\(/.test(resolved)) return `calc(-1 * (${resolved}))`;
790
794
  }
@@ -963,7 +967,7 @@ function makeBorderSideResolver(widthProp, colorProp) {
963
967
  if (!value) return { [widthProp]: 1 };
964
968
  if (isArbitrary) {
965
969
  const w2 = toNativeValue(value);
966
- if (typeof w2 === "number") return { [widthProp]: w2 };
970
+ if (typeof w2 === "number") return { [widthProp]: getEffectiveIsWeb() ? value : w2 };
967
971
  return { [colorProp]: value };
968
972
  }
969
973
  const color = resolveColor(value, colors, false);
@@ -984,7 +988,7 @@ var borderResolvers = {
984
988
  if (!value) return { borderWidth: borderWidth["DEFAULT"] ?? 1 };
985
989
  if (isArbitrary) {
986
990
  const w2 = toNativeValue(value);
987
- if (typeof w2 === "number") return { borderWidth: w2 };
991
+ if (typeof w2 === "number") return { borderWidth: getEffectiveIsWeb() ? value : w2 };
988
992
  return { borderColor: value };
989
993
  }
990
994
  const color = resolveColor(value, colors, false);
@@ -1183,9 +1187,9 @@ var filterResolvers = {
1183
1187
  const v = isArbitrary ? `contrast(${value})` : `contrast(${parseFloat(value) / 100})`;
1184
1188
  return isNaN(parseFloat(value)) && !isArbitrary ? null : { "--kb-contrast": v, filter: FILTER_COMPOSE };
1185
1189
  },
1186
- grayscale: ({ value }) => {
1190
+ grayscale: ({ value, isArbitrary }) => {
1187
1191
  if (!getEffectiveIsWeb()) return null;
1188
- const v = value === "0" ? "grayscale(0)" : "grayscale(100%)";
1192
+ const v = isArbitrary ? `grayscale(${value})` : value === "0" ? "grayscale(0)" : "grayscale(100%)";
1189
1193
  return { "--kb-grayscale": v, filter: FILTER_COMPOSE };
1190
1194
  },
1191
1195
  "hue-rotate": ({ value, negative, isArbitrary }) => {
@@ -1194,9 +1198,9 @@ var filterResolvers = {
1194
1198
  if (isNaN(parseFloat(deg)) && !isArbitrary) return null;
1195
1199
  return { "--kb-hue-rotate": `hue-rotate(${deg})`, filter: FILTER_COMPOSE };
1196
1200
  },
1197
- invert: ({ value }) => {
1201
+ invert: ({ value, isArbitrary }) => {
1198
1202
  if (!getEffectiveIsWeb()) return null;
1199
- const v = value === "0" ? "invert(0)" : "invert(100%)";
1203
+ const v = isArbitrary ? `invert(${value})` : value === "0" ? "invert(0)" : "invert(100%)";
1200
1204
  return { "--kb-invert": v, filter: FILTER_COMPOSE };
1201
1205
  },
1202
1206
  saturate: ({ value, isArbitrary }) => {
@@ -1204,9 +1208,9 @@ var filterResolvers = {
1204
1208
  const v = isArbitrary ? `saturate(${value})` : `saturate(${parseFloat(value) / 100})`;
1205
1209
  return isNaN(parseFloat(value)) && !isArbitrary ? null : { "--kb-saturate": v, filter: FILTER_COMPOSE };
1206
1210
  },
1207
- sepia: ({ value }) => {
1211
+ sepia: ({ value, isArbitrary }) => {
1208
1212
  if (!getEffectiveIsWeb()) return null;
1209
- const v = value === "0" ? "sepia(0)" : "sepia(100%)";
1213
+ const v = isArbitrary ? `sepia(${value})` : value === "0" ? "sepia(0)" : "sepia(100%)";
1210
1214
  return { "--kb-sepia": v, filter: FILTER_COMPOSE };
1211
1215
  },
1212
1216
  "drop-shadow": ({ value, isArbitrary }) => {
@@ -1257,9 +1261,10 @@ var filterResolvers = {
1257
1261
  const v = isArbitrary ? `contrast(${value})` : `contrast(${parseFloat(value) / 100})`;
1258
1262
  return isNaN(parseFloat(value)) && !isArbitrary ? null : { "--kb-backdrop-contrast": v, backdropFilter: BACKDROP_FILTER_COMPOSE };
1259
1263
  },
1260
- "backdrop-grayscale": ({ value }) => {
1264
+ "backdrop-grayscale": ({ value, isArbitrary }) => {
1261
1265
  if (!getEffectiveIsWeb()) return null;
1262
- return { "--kb-backdrop-grayscale": value === "0" ? "grayscale(0)" : "grayscale(100%)", backdropFilter: BACKDROP_FILTER_COMPOSE };
1266
+ const v = isArbitrary ? `grayscale(${value})` : value === "0" ? "grayscale(0)" : "grayscale(100%)";
1267
+ return { "--kb-backdrop-grayscale": v, backdropFilter: BACKDROP_FILTER_COMPOSE };
1263
1268
  },
1264
1269
  "backdrop-hue-rotate": ({ value, negative, isArbitrary }) => {
1265
1270
  if (!getEffectiveIsWeb()) return null;
@@ -1267,9 +1272,10 @@ var filterResolvers = {
1267
1272
  if (isNaN(parseFloat(deg)) && !isArbitrary) return null;
1268
1273
  return { "--kb-backdrop-hue-rotate": `hue-rotate(${deg})`, backdropFilter: BACKDROP_FILTER_COMPOSE };
1269
1274
  },
1270
- "backdrop-invert": ({ value }) => {
1275
+ "backdrop-invert": ({ value, isArbitrary }) => {
1271
1276
  if (!getEffectiveIsWeb()) return null;
1272
- return { "--kb-backdrop-invert": value === "0" ? "invert(0)" : "invert(100%)", backdropFilter: BACKDROP_FILTER_COMPOSE };
1277
+ const v = isArbitrary ? `invert(${value})` : value === "0" ? "invert(0)" : "invert(100%)";
1278
+ return { "--kb-backdrop-invert": v, backdropFilter: BACKDROP_FILTER_COMPOSE };
1273
1279
  },
1274
1280
  "backdrop-opacity": ({ value, isArbitrary }) => {
1275
1281
  if (!getEffectiveIsWeb()) return null;
@@ -1281,9 +1287,10 @@ var filterResolvers = {
1281
1287
  const v = isArbitrary ? `saturate(${value})` : `saturate(${parseFloat(value) / 100})`;
1282
1288
  return isNaN(parseFloat(value)) && !isArbitrary ? null : { "--kb-backdrop-saturate": v, backdropFilter: BACKDROP_FILTER_COMPOSE };
1283
1289
  },
1284
- "backdrop-sepia": ({ value }) => {
1290
+ "backdrop-sepia": ({ value, isArbitrary }) => {
1285
1291
  if (!getEffectiveIsWeb()) return null;
1286
- return { "--kb-backdrop-sepia": value === "0" ? "sepia(0)" : "sepia(100%)", backdropFilter: BACKDROP_FILTER_COMPOSE };
1292
+ const v = isArbitrary ? `sepia(${value})` : value === "0" ? "sepia(0)" : "sepia(100%)";
1293
+ return { "--kb-backdrop-sepia": v, backdropFilter: BACKDROP_FILTER_COMPOSE };
1287
1294
  },
1288
1295
  "backdrop-filter": ({ value, isArbitrary }) => {
1289
1296
  if (!getEffectiveIsWeb()) return null;
@@ -1353,8 +1360,8 @@ var filterResolvers = {
1353
1360
 
1354
1361
  // src/core/resolvers/layout.ts
1355
1362
  var _standalone = null;
1356
- function buildStandalone() {
1357
- const web = getEffectiveIsWeb();
1363
+ var _standaloneWeb = null;
1364
+ function buildStandalone(web) {
1358
1365
  return {
1359
1366
  // Display
1360
1367
  // React Native only supports display:'flex'|'none'. Setting 'flex' explicitly is
@@ -1369,7 +1376,6 @@ function buildStandalone() {
1369
1376
  "inline-block": { display: web ? "inline-block" : "flex" },
1370
1377
  inline: { display: web ? "inline" : "flex" },
1371
1378
  grid: web ? { display: "grid" } : null,
1372
- grd: web ? { display: "grid" } : null,
1373
1379
  "inline-flex": { display: web ? "inline-flex" : "flex" },
1374
1380
  "inline-grid": { display: web ? "inline-grid" : "flex" },
1375
1381
  hidden: { display: "none" },
@@ -1721,7 +1727,11 @@ function buildStandalone() {
1721
1727
  };
1722
1728
  }
1723
1729
  function getStandalone() {
1724
- if (!_standalone) _standalone = buildStandalone();
1730
+ const web = getEffectiveIsWeb();
1731
+ if (!_standalone || _standaloneWeb !== web) {
1732
+ _standalone = buildStandalone(web);
1733
+ _standaloneWeb = web;
1734
+ }
1725
1735
  return _standalone;
1726
1736
  }
1727
1737
  var layoutResolvers = {
@@ -2285,6 +2295,18 @@ var effectResolvers = {
2285
2295
  if (!isArbitrary && isNaN(parseFloat(value))) return null;
2286
2296
  const ms = isArbitrary ? value : `${value}ms`;
2287
2297
  return { transitionDelay: ms };
2298
+ },
2299
+ ease: ({ value, isArbitrary }) => {
2300
+ if (!getEffectiveIsWeb()) return null;
2301
+ if (isArbitrary) return { transitionTimingFunction: value };
2302
+ const presets = {
2303
+ linear: "linear",
2304
+ in: "cubic-bezier(0.4, 0, 1, 1)",
2305
+ out: "cubic-bezier(0, 0, 0.2, 1)",
2306
+ "in-out": "cubic-bezier(0.4, 0, 0.2, 1)"
2307
+ };
2308
+ const v = presets[value];
2309
+ return v !== void 0 ? { transitionTimingFunction: v } : null;
2288
2310
  }
2289
2311
  };
2290
2312
 
@@ -2477,64 +2499,64 @@ var LRUCache = class {
2477
2499
  // src/core/registry.ts
2478
2500
  var BUILTIN_MODIFIERS = {
2479
2501
  // ── Mode ─────────────────────────────────────────────────────────────────
2480
- dark: { darkScheme: "dark", jsBehavior: "mode", jsMatch: (d) => d },
2481
- "not-light": { darkScheme: "dark", jsBehavior: "mode", jsMatch: (d) => d },
2482
- light: { darkScheme: "light", jsBehavior: "mode", jsMatch: (d) => !d },
2483
- "not-dark": { darkScheme: "light", jsBehavior: "mode", jsMatch: (d) => !d },
2502
+ dark: { order: 60, darkScheme: "dark", jsBehavior: "mode", jsMatch: (d) => d },
2503
+ "not-light": { order: 60, darkScheme: "dark", jsBehavior: "mode", jsMatch: (d) => d },
2504
+ light: { order: 60, darkScheme: "light", jsBehavior: "mode", jsMatch: (d) => !d },
2505
+ "not-dark": { order: 60, darkScheme: "light", jsBehavior: "mode", jsMatch: (d) => !d },
2484
2506
  // ── Interactive — JS state + CSS pseudo ───────────────────────────────────
2485
- hover: { pseudo: ":hover", jsBehavior: "interactive", jsMatch: (_, s) => !!s.hover },
2486
- "not-hover": { pseudo: ":not(:hover)", jsBehavior: "interactive", jsMatch: (_, s) => !s.hover },
2487
- focus: { pseudo: ":focus", jsBehavior: "interactive", jsMatch: (_, s) => !!s.focus },
2488
- "not-focus": { pseudo: ":not(:focus)", jsBehavior: "interactive", jsMatch: (_, s) => !s.focus },
2489
- active: { pseudo: ":active", jsBehavior: "interactive", jsMatch: (_, s) => !!s.active || !!s.pressed },
2490
- "not-active": { pseudo: ":not(:active)", jsBehavior: "interactive", jsMatch: (_, s) => !s.active && !s.pressed },
2491
- pressed: { pseudo: ":active", jsBehavior: "interactive", jsMatch: (_, s) => !!s.pressed || !!s.active },
2492
- "not-pressed": { pseudo: ":not(:active)", jsBehavior: "interactive", jsMatch: (_, s) => !s.pressed && !s.active },
2493
- disabled: { pseudo: ":disabled", jsBehavior: "interactive", jsMatch: (_, s) => !!s.disabled },
2494
- "not-disabled": { pseudo: ":not(:disabled)", jsBehavior: "interactive", jsMatch: (_, s) => !s.disabled },
2495
- checked: { pseudo: ":checked", jsBehavior: "interactive", jsMatch: (_, s) => !!s.checked },
2496
- "not-checked": { pseudo: ":not(:checked)", jsBehavior: "interactive", jsMatch: (_, s) => !s.checked },
2497
- visited: { pseudo: ":visited", jsBehavior: "interactive", jsMatch: (_, s) => !!s.visited },
2498
- "not-visited": { pseudo: ":not(:visited)", jsBehavior: "interactive", jsMatch: (_, s) => !s.visited },
2499
- placeholder: { pseudo: "::placeholder", jsBehavior: "interactive", jsMatch: (_, s) => !!s.placeholder },
2507
+ hover: { order: 10, pseudo: ":hover", jsBehavior: "interactive", jsMatch: (_, s) => !!s.hover },
2508
+ "not-hover": { order: 10, pseudo: ":not(:hover)", jsBehavior: "interactive", jsMatch: (_, s) => !s.hover },
2509
+ focus: { order: 20, pseudo: ":focus", jsBehavior: "interactive", jsMatch: (_, s) => !!s.focus },
2510
+ "not-focus": { order: 20, pseudo: ":not(:focus)", jsBehavior: "interactive", jsMatch: (_, s) => !s.focus },
2511
+ active: { order: 30, pseudo: ":active", jsBehavior: "interactive", jsMatch: (_, s) => !!s.active || !!s.pressed },
2512
+ "not-active": { order: 30, pseudo: ":not(:active)", jsBehavior: "interactive", jsMatch: (_, s) => !s.active && !s.pressed },
2513
+ pressed: { order: 30, pseudo: ":active", jsBehavior: "interactive", jsMatch: (_, s) => !!s.pressed || !!s.active },
2514
+ "not-pressed": { order: 30, pseudo: ":not(:active)", jsBehavior: "interactive", jsMatch: (_, s) => !s.pressed && !s.active },
2515
+ disabled: { order: 40, pseudo: ":disabled", jsBehavior: "interactive", jsMatch: (_, s) => !!s.disabled },
2516
+ "not-disabled": { order: 40, pseudo: ":not(:disabled)", jsBehavior: "interactive", jsMatch: (_, s) => !s.disabled },
2517
+ checked: { order: 35, pseudo: ":checked", jsBehavior: "interactive", jsMatch: (_, s) => !!s.checked },
2518
+ "not-checked": { order: 35, pseudo: ":not(:checked)", jsBehavior: "interactive", jsMatch: (_, s) => !s.checked },
2519
+ visited: { order: 5, pseudo: ":visited", jsBehavior: "interactive", jsMatch: (_, s) => !!s.visited },
2520
+ "not-visited": { order: 5, pseudo: ":not(:visited)", jsBehavior: "interactive", jsMatch: (_, s) => !s.visited },
2521
+ placeholder: { order: 0, pseudo: "::placeholder", jsBehavior: "interactive", jsMatch: (_, s) => !!s.placeholder },
2500
2522
  // ── CSS-only pseudo-classes (structural) ──────────────────────────────────
2501
- first: { pseudo: ":first-child", jsBehavior: "css-only", forcesImportant: true },
2502
- last: { pseudo: ":last-child", jsBehavior: "css-only", forcesImportant: true },
2503
- odd: { pseudo: ":nth-child(odd)", jsBehavior: "css-only", forcesImportant: true },
2504
- even: { pseudo: ":nth-child(even)", jsBehavior: "css-only", forcesImportant: true },
2505
- only: { pseudo: ":only-child", jsBehavior: "css-only", forcesImportant: true },
2506
- "focus-within": { pseudo: ":focus-within", jsBehavior: "css-only", forcesImportant: true },
2507
- "focus-visible": { pseudo: ":focus-visible", jsBehavior: "css-only", forcesImportant: true },
2523
+ first: { order: 0, pseudo: ":first-child", jsBehavior: "css-only", forcesImportant: true },
2524
+ last: { order: 0, pseudo: ":last-child", jsBehavior: "css-only", forcesImportant: true },
2525
+ odd: { order: 0, pseudo: ":nth-child(odd)", jsBehavior: "css-only", forcesImportant: true },
2526
+ even: { order: 0, pseudo: ":nth-child(even)", jsBehavior: "css-only", forcesImportant: true },
2527
+ only: { order: 0, pseudo: ":only-child", jsBehavior: "css-only", forcesImportant: true },
2528
+ "focus-within": { order: 15, pseudo: ":focus-within", jsBehavior: "css-only", forcesImportant: true },
2529
+ "focus-visible": { order: 25, pseudo: ":focus-visible", jsBehavior: "css-only", forcesImportant: true },
2508
2530
  // ── CSS-only pseudo-elements ───────────────────────────────────────────────
2509
- before: { pseudo: "::before", jsBehavior: "css-only" },
2510
- after: { pseudo: "::after", jsBehavior: "css-only" },
2511
- selection: { pseudo: "::selection", jsBehavior: "css-only" },
2512
- "first-letter": { pseudo: "::first-letter", jsBehavior: "css-only" },
2513
- "first-line": { pseudo: "::first-line", jsBehavior: "css-only" },
2514
- marker: { pseudo: "::marker", jsBehavior: "css-only" },
2531
+ before: { order: 0, pseudo: "::before", jsBehavior: "css-only" },
2532
+ after: { order: 0, pseudo: "::after", jsBehavior: "css-only" },
2533
+ selection: { order: 0, pseudo: "::selection", jsBehavior: "css-only" },
2534
+ "first-letter": { order: 0, pseudo: "::first-letter", jsBehavior: "css-only" },
2535
+ "first-line": { order: 0, pseudo: "::first-line", jsBehavior: "css-only" },
2536
+ marker: { order: 0, pseudo: "::marker", jsBehavior: "css-only" },
2515
2537
  // ── Group / peer ancestor selectors (CSS-only) ─────────────────────────────
2516
- "group-hover": { ancestorSelector: ".group:hover ", jsBehavior: "css-only", forcesImportant: true },
2517
- "group-focus": { ancestorSelector: ".group:focus ", jsBehavior: "css-only", forcesImportant: true },
2518
- "peer-hover": { ancestorSelector: ".peer:hover ~ ", jsBehavior: "css-only", forcesImportant: true },
2519
- "peer-focus": { ancestorSelector: ".peer:focus ~ ", jsBehavior: "css-only", forcesImportant: true },
2538
+ "group-hover": { order: 10, ancestorSelector: ".group:hover ", jsBehavior: "css-only", forcesImportant: true },
2539
+ "group-focus": { order: 20, ancestorSelector: ".group:focus ", jsBehavior: "css-only", forcesImportant: true },
2540
+ "peer-hover": { order: 10, ancestorSelector: ".peer:hover ~ ", jsBehavior: "css-only", forcesImportant: true },
2541
+ "peer-focus": { order: 20, ancestorSelector: ".peer:focus ~ ", jsBehavior: "css-only", forcesImportant: true },
2520
2542
  // ── Responsive — both CSS (@media min-width) and JS (breakpoints set) ──────
2521
- sm: { isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("sm") },
2522
- md: { isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("md") },
2523
- lg: { isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("lg") },
2524
- xl: { isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("xl") },
2525
- "2xl": { isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("2xl") },
2543
+ sm: { order: 50, isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("sm") },
2544
+ md: { order: 50, isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("md") },
2545
+ lg: { order: 50, isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("lg") },
2546
+ xl: { order: 50, isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("xl") },
2547
+ "2xl": { order: 50, isResponsive: true, jsBehavior: "responsive", jsMatch: (_, __, bp) => bp.has("2xl") },
2526
2548
  // ── Print media (CSS-only) ─────────────────────────────────────────────────
2527
- print: { mediaQuery: "print", jsBehavior: "css-only", forcesImportant: true },
2549
+ print: { order: 70, mediaQuery: "print", jsBehavior: "css-only", forcesImportant: true },
2528
2550
  // ── Orientation / accessibility media (CSS-only) ───────────────────────────
2529
- landscape: { mediaQuery: "(orientation: landscape)", jsBehavior: "css-only", forcesImportant: true },
2530
- portrait: { mediaQuery: "(orientation: portrait)", jsBehavior: "css-only", forcesImportant: true },
2531
- "motion-reduce": { mediaQuery: "(prefers-reduced-motion: reduce)", jsBehavior: "css-only", forcesImportant: true },
2532
- "motion-safe": { mediaQuery: "(prefers-reduced-motion: no-preference)", jsBehavior: "css-only", forcesImportant: true },
2533
- "contrast-more": { mediaQuery: "(prefers-contrast: more)", jsBehavior: "css-only", forcesImportant: true },
2534
- "contrast-less": { mediaQuery: "(prefers-contrast: less)", jsBehavior: "css-only", forcesImportant: true },
2551
+ landscape: { order: 70, mediaQuery: "(orientation: landscape)", jsBehavior: "css-only", forcesImportant: true },
2552
+ portrait: { order: 70, mediaQuery: "(orientation: portrait)", jsBehavior: "css-only", forcesImportant: true },
2553
+ "motion-reduce": { order: 70, mediaQuery: "(prefers-reduced-motion: reduce)", jsBehavior: "css-only", forcesImportant: true },
2554
+ "motion-safe": { order: 70, mediaQuery: "(prefers-reduced-motion: no-preference)", jsBehavior: "css-only", forcesImportant: true },
2555
+ "contrast-more": { order: 70, mediaQuery: "(prefers-contrast: more)", jsBehavior: "css-only", forcesImportant: true },
2556
+ "contrast-less": { order: 70, mediaQuery: "(prefers-contrast: less)", jsBehavior: "css-only", forcesImportant: true },
2535
2557
  // ── Directionality (CSS-only) ──────────────────────────────────────────────
2536
- rtl: { dirSelector: '[dir="rtl"] ', jsBehavior: "css-only", forcesImportant: true },
2537
- ltr: { dirSelector: '[dir="ltr"] ', jsBehavior: "css-only", forcesImportant: true }
2558
+ rtl: { order: 80, dirSelector: '[dir="rtl"] ', jsBehavior: "css-only", forcesImportant: true },
2559
+ ltr: { order: 80, dirSelector: '[dir="ltr"] ', jsBehavior: "css-only", forcesImportant: true }
2538
2560
  };
2539
2561
  var _pluginModifiers = {};
2540
2562
  var _allNames = null;
@@ -2571,6 +2593,8 @@ function getNamedGroupPeerModifier(name) {
2571
2593
  const pseudo = trigger === "hover" ? ":hover" : ":focus";
2572
2594
  const combinator = kind === "group" ? " " : " ~ ";
2573
2595
  const def = {
2596
+ // Matches the plain group-hover/group-focus/peer-hover/peer-focus order below.
2597
+ order: trigger === "hover" ? 10 : 20,
2574
2598
  ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
2575
2599
  jsBehavior: "css-only",
2576
2600
  forcesImportant: true
@@ -2584,6 +2608,15 @@ function getModifier(name) {
2584
2608
  function isKnownModifier(name) {
2585
2609
  return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
2586
2610
  }
2611
+ function getModifierOrder(bucketKey) {
2612
+ if (bucketKey === "base") return -1;
2613
+ let max = 0;
2614
+ for (const mod of bucketKey.split(":")) {
2615
+ const order = getModifier(mod)?.order ?? 0;
2616
+ if (order > max) max = order;
2617
+ }
2618
+ return max;
2619
+ }
2587
2620
 
2588
2621
  // src/core/parser.ts
2589
2622
  var UNSAFE_ARBITRARY_VALUE = /[{};\\]|\/\*|\*\//;
@@ -2796,9 +2829,12 @@ function setDefaultFontFamily(font) {
2796
2829
  }
2797
2830
  var _styleEl = null;
2798
2831
  var _ruleIndexByKey = /* @__PURE__ */ new Map();
2832
+ var _sheetKeys = [];
2833
+ var _ruleOrderByKey = /* @__PURE__ */ new Map();
2799
2834
  function evictInjectedRule(rule) {
2800
2835
  const idx = _ruleIndexByKey.get(rule);
2801
2836
  _ruleIndexByKey.delete(rule);
2837
+ _ruleOrderByKey.delete(rule);
2802
2838
  if (idx === void 0) return;
2803
2839
  const sheet = _styleEl?.sheet;
2804
2840
  if (!sheet) return;
@@ -2807,6 +2843,7 @@ function evictInjectedRule(rule) {
2807
2843
  } catch {
2808
2844
  return;
2809
2845
  }
2846
+ _sheetKeys.splice(idx, 1);
2810
2847
  for (const [key, i] of _ruleIndexByKey) {
2811
2848
  if (i > idx) _ruleIndexByKey.set(key, i - 1);
2812
2849
  }
@@ -3037,7 +3074,6 @@ function deepMerge(base, override) {
3037
3074
  }
3038
3075
  return result;
3039
3076
  }
3040
- var configStore = { resolved: null, listeners: /* @__PURE__ */ new Set(), customVariants: {} };
3041
3077
  function resolveColorRefs(colors) {
3042
3078
  const MAX_DEPTH = 5;
3043
3079
  function resolveChain(raw, side) {
@@ -3071,10 +3107,9 @@ function resolveColorRefs(colors) {
3071
3107
  const a = opacity > 1 ? opacity / 100 : opacity;
3072
3108
  return hexToRgba(baseHex, a);
3073
3109
  }
3074
- const lastDash = ref.lastIndexOf("-");
3075
- if (lastDash <= 0) return null;
3076
- const name = ref.slice(0, lastDash);
3077
- const shade = ref.slice(lastDash + 1);
3110
+ const split = splitColorShadeRef(ref);
3111
+ if (!split) return null;
3112
+ const { name, shade } = split;
3078
3113
  if (!/^\d+$/.test(shade)) return null;
3079
3114
  const entry = colors[name];
3080
3115
  if (!entry || typeof entry !== "object" || isModeAwareColor(entry)) return null;
@@ -3136,7 +3171,6 @@ function buildConfig(userConfig) {
3136
3171
  clearPluginUtilities();
3137
3172
  clearPluginModifiers();
3138
3173
  }
3139
- for (const k of Object.keys(configStore.customVariants)) delete configStore.customVariants[k];
3140
3174
  const pluginAPI = makePluginAPI(resolved.theme);
3141
3175
  for (const plugin of resolved.plugins) {
3142
3176
  plugin(pluginAPI);
@@ -3165,8 +3199,6 @@ function makePluginAPI(theme) {
3165
3199
  },
3166
3200
  addVariant(name, selectorOrDef) {
3167
3201
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
3168
- const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? null;
3169
- if (selector !== null && selector !== name) customVariants[name] = selector;
3170
3202
  registerModifier(name, def);
3171
3203
  },
3172
3204
  theme(path, defaultValue) {
@@ -3183,7 +3215,6 @@ function makePluginAPI(theme) {
3183
3215
  }
3184
3216
  };
3185
3217
  }
3186
- var customVariants = configStore.customVariants;
3187
3218
 
3188
3219
  // src/core/generateTypesDts.ts
3189
3220
  var HEADER = `// AUTO-GENERATED by Kbach from your kbach.config.js \u2014 do not edit by hand.
@@ -3285,7 +3316,8 @@ var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borde
3285
3316
  function classifyToken(token, responsiveRe) {
3286
3317
  if (/^dark:/.test(token)) return "Dark Mode";
3287
3318
  if (responsiveRe.test(token)) return "Responsive";
3288
- const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
3319
+ const parsed = parseClass(token);
3320
+ const base = parsed ? parsed.value ? `${parsed.utility}-${parsed.value}` : parsed.utility : token;
3289
3321
  for (const [label, re] of GROUPS) {
3290
3322
  if (re.test(base)) return label;
3291
3323
  }
@@ -3421,7 +3453,9 @@ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
3421
3453
  if (!css) continue;
3422
3454
  const label = classifyToken(token, responsiveRe);
3423
3455
  if (!groups.has(label)) groups.set(label, []);
3424
- groups.get(label).push(prettifyCSS(applyVars(css)));
3456
+ const parsedToken = parseClass(token);
3457
+ const bucketKey = parsedToken && parsedToken.modifiers.length > 0 ? parsedToken.modifiers.join(":") : "base";
3458
+ groups.get(label).push({ order: getModifierOrder(bucketKey), css: prettifyCSS(applyVars(css)) });
3425
3459
  }
3426
3460
  const out = ["/* Generated by Kbach \u2014 do not edit */"];
3427
3461
  if (declarations.size > 0) {
@@ -3436,9 +3470,10 @@ ${prettifyCSS(globalCSS)}`);
3436
3470
  for (const label of GROUP_ORDER) {
3437
3471
  const items = groups.get(label) ?? [];
3438
3472
  if (!items.length) continue;
3473
+ const sorted = [...items].sort((a, b) => a.order - b.order);
3439
3474
  out.push(`
3440
3475
  /* ${label} */`);
3441
- out.push(items.join("\n\n"));
3476
+ out.push(sorted.map((it) => it.css).join("\n\n"));
3442
3477
  }
3443
3478
  return out.join("\n");
3444
3479
  }
@@ -3707,6 +3742,13 @@ function kbach(userConfigOrOptions) {
3707
3742
  function scanProjectCssSelectors() {
3708
3743
  for (const dir of includeDirs) scanCssSelectorsInto(join(root, dir), projectCssClasses);
3709
3744
  }
3745
+ function isWithinIncludeDirs(file) {
3746
+ const nf = normPath(file);
3747
+ return includeDirs.some((dir) => {
3748
+ const base = normPath(join(root, dir));
3749
+ return nf === base || nf.startsWith(base.endsWith("/") ? base : `${base}/`);
3750
+ });
3751
+ }
3710
3752
  function buildTokenCSSView() {
3711
3753
  const active = /* @__PURE__ */ new Set();
3712
3754
  for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
@@ -3808,6 +3850,7 @@ ${code}`,
3808
3850
  configureServer(server) {
3809
3851
  const onAddOrUnlink = (file, isUnlink) => {
3810
3852
  if (file.includes("node_modules")) return;
3853
+ if (!isWithinIncludeDirs(file)) return;
3811
3854
  if (CSS_FILE_RE.test(file)) {
3812
3855
  if (!isUnlink) scanCssFileInto(file, projectCssClasses);
3813
3856
  return;
@@ -3829,6 +3872,7 @@ ${code}`,
3829
3872
  },
3830
3873
  handleHotUpdate({ file, server }) {
3831
3874
  if (file.includes("node_modules")) return;
3875
+ if (!isWithinIncludeDirs(file)) return;
3832
3876
  if (CSS_FILE_RE.test(file)) {
3833
3877
  scanCssFileInto(file, projectCssClasses);
3834
3878
  return;
@@ -3844,5 +3888,6 @@ ${code}`,
3844
3888
  };
3845
3889
  }
3846
3890
  export {
3891
+ formatKbachCSS,
3847
3892
  kbach
3848
3893
  };
package/kbach-react.md CHANGED
@@ -619,6 +619,7 @@ animate-spin / animate-ping / animate-pulse / animate-bounce / animate-none
619
619
  transition / transition-all/none/colors/opacity/shadow/transform
620
620
  duration-75/100/150/200/300/500/700/1000
621
621
  delay-75/100/150/200/300/500/700/1000
622
+ ease-linear/in/out/in-out ease-[cubic-bezier(...)]
622
623
  ```
623
624
 
624
625
  ### Cursor
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.37",
3
+ "version": "0.6.40",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
+ "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/semsakadanupol/kbach.git",
@@ -49,8 +50,12 @@
49
50
  "postinstall": "node scripts/postinstall.js"
50
51
  },
51
52
  "devDependencies": {
53
+ "@testing-library/react": "^16.3.2",
52
54
  "@types/node": "^20.19.42",
53
55
  "@types/react": "^19.2.0",
56
+ "@types/react-dom": "^19.2.4",
57
+ "jsdom": "^30.0.1",
58
+ "react-dom": "^19.2.3",
54
59
  "tsup": "^8.0.0",
55
60
  "vite": "^5.4.21",
56
61
  "vitest": "^1.0.0"