@kbach/react 0.3.4 → 0.3.6

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.
@@ -539,10 +539,14 @@ var defaultTheme = {
539
539
  }
540
540
  },
541
541
  screens: {
542
- sm: 640,
542
+ sm: 390,
543
+ // large phones (iPhone 12+, most Android flagships)
543
544
  md: 768,
545
+ // tablets (iPad and up)
544
546
  lg: 1024,
547
+ // large tablets (iPad Pro)
545
548
  xl: 1280,
549
+ // desktop
546
550
  "2xl": 1536
547
551
  }
548
552
  };
@@ -1870,6 +1874,36 @@ function getPluginStandaloneMap() {
1870
1874
  return PLUGIN_STANDALONE;
1871
1875
  }
1872
1876
 
1877
+ // src/core/responsiveStore.ts
1878
+ var RESPONSIVE_KEY = "__kbach_responsive__";
1879
+ function getStore() {
1880
+ const g = globalThis;
1881
+ if (!g[RESPONSIVE_KEY]) {
1882
+ g[RESPONSIVE_KEY] = { width: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
1883
+ }
1884
+ return g[RESPONSIVE_KEY];
1885
+ }
1886
+ function getGlobalScreens() {
1887
+ return getStore().screens;
1888
+ }
1889
+ function getGlobalWidth() {
1890
+ return getStore().width;
1891
+ }
1892
+ function subscribeGlobalWidth(listener) {
1893
+ const store = getStore();
1894
+ store.listeners.add(listener);
1895
+ return () => store.listeners.delete(listener);
1896
+ }
1897
+ function getActiveBreakpoints(width) {
1898
+ const store = getStore();
1899
+ const w = width ?? store.width;
1900
+ const active = /* @__PURE__ */ new Set();
1901
+ for (const [name, minW] of Object.entries(store.screens)) {
1902
+ if (typeof minW === "number" && w >= minW) active.add(name);
1903
+ }
1904
+ return active;
1905
+ }
1906
+
1873
1907
  // src/core/resolver.ts
1874
1908
  var styleCache = new LRUCache(1e4);
1875
1909
  var _styleEl = null;
@@ -1920,6 +1954,7 @@ var GROUP_PEER_PREFIX = {
1920
1954
  "peer-focus": ".peer:focus ~"
1921
1955
  };
1922
1956
  var MODE_MODS = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
1957
+ var RESPONSIVE_MODS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1923
1958
  function injectClassRule(cls, bucketKey, styles, darkMode) {
1924
1959
  const cssDecls = styleValueToCSS(styles);
1925
1960
  if (!cssDecls) return;
@@ -1929,10 +1964,12 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1929
1964
  return;
1930
1965
  }
1931
1966
  const mods = bucketKey.split(":");
1932
- const hasDark = mods.includes("dark") || mods.includes("not-light");
1933
- const hasLight = mods.includes("light") || mods.includes("not-dark");
1934
- const ancestorMods = mods.filter((m) => m in GROUP_PEER_PREFIX);
1935
- const pseudoMods = mods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
1967
+ const responsiveMods = mods.filter((m) => RESPONSIVE_MODS.has(m));
1968
+ const nonResponsiveMods = mods.filter((m) => !RESPONSIVE_MODS.has(m));
1969
+ const hasDark = nonResponsiveMods.includes("dark") || nonResponsiveMods.includes("not-light");
1970
+ const hasLight = nonResponsiveMods.includes("light") || nonResponsiveMods.includes("not-dark");
1971
+ const ancestorMods = nonResponsiveMods.filter((m) => m in GROUP_PEER_PREFIX);
1972
+ const pseudoMods = nonResponsiveMods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
1936
1973
  const pseudoSuffix = pseudoMods.map((p) => MODIFIER_TO_PSEUDO[p] ?? `:${p}`).join("");
1937
1974
  const elementSelector = `.${escaped}${pseudoSuffix}`;
1938
1975
  const ancestorPrefix = ancestorMods.length ? ancestorMods.map((m) => GROUP_PEER_PREFIX[m]).join(" ") + " " : "";
@@ -1957,6 +1994,13 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1957
1994
  } else {
1958
1995
  rule = `${selector} { ${cssDecls} }`;
1959
1996
  }
1997
+ if (responsiveMods.length > 0) {
1998
+ const screens = getGlobalScreens();
1999
+ const minWidth = Math.max(...responsiveMods.map((m) => screens[m] ?? 0));
2000
+ if (minWidth > 0) {
2001
+ rule = `@media (min-width: ${minWidth}px) { ${rule} }`;
2002
+ }
2003
+ }
1960
2004
  injectRule(rule);
1961
2005
  }
1962
2006
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2025,18 +2069,18 @@ function resolve(classString, theme, darkMode = "attribute") {
2025
2069
  }
2026
2070
  return result;
2027
2071
  }
2028
- function flatten(resolved, isDark, state = {}) {
2072
+ function flatten(resolved, isDark, state = {}, breakpoints = /* @__PURE__ */ new Set()) {
2029
2073
  const result = { ...resolved.base ?? {} };
2030
2074
  for (const [key, styles] of Object.entries(resolved)) {
2031
2075
  if (key === "base" || !styles) continue;
2032
2076
  const mods = key.split(":");
2033
- if (matchesMods(mods, isDark, state)) {
2077
+ if (matchesMods(mods, isDark, state, breakpoints)) {
2034
2078
  Object.assign(result, styles);
2035
2079
  }
2036
2080
  }
2037
2081
  return result;
2038
2082
  }
2039
- function matchesMods(mods, isDark, state) {
2083
+ function matchesMods(mods, isDark, state, breakpoints) {
2040
2084
  return mods.every((mod) => {
2041
2085
  switch (mod) {
2042
2086
  case "dark":
@@ -2074,6 +2118,12 @@ function matchesMods(mods, isDark, state) {
2074
2118
  return !state.visited;
2075
2119
  case "placeholder":
2076
2120
  return !!state.placeholder;
2121
+ case "sm":
2122
+ case "md":
2123
+ case "lg":
2124
+ case "xl":
2125
+ case "2xl":
2126
+ return breakpoints.has(mod);
2077
2127
  default:
2078
2128
  return false;
2079
2129
  }
@@ -2212,7 +2262,7 @@ var customVariants = new Proxy(
2212
2262
 
2213
2263
  // src/core/darkModeStore.ts
2214
2264
  var KEY = "__kbach_dark_store__";
2215
- function getStore() {
2265
+ function getStore2() {
2216
2266
  const g = globalThis;
2217
2267
  if (!g[KEY]) {
2218
2268
  g[KEY] = { isDark: false, lastNotified: false, subscribers: /* @__PURE__ */ new Set() };
@@ -2220,16 +2270,16 @@ function getStore() {
2220
2270
  return g[KEY];
2221
2271
  }
2222
2272
  function getGlobalDarkMode() {
2223
- return getStore().isDark;
2273
+ return getStore2().isDark;
2224
2274
  }
2225
2275
  function subscribeGlobalDarkMode(callback) {
2226
- const store = getStore();
2276
+ const store = getStore2();
2227
2277
  store.subscribers.add(callback);
2228
2278
  return () => store.subscribers.delete(callback);
2229
2279
  }
2230
2280
 
2231
2281
  // src/InteractiveWrapper.tsx
2232
- var import_react2 = __toESM(require("react"));
2282
+ var import_react3 = __toESM(require("react"));
2233
2283
 
2234
2284
  // src/useGlobalDarkMode.ts
2235
2285
  var import_react = __toESM(require("react"));
@@ -2247,6 +2297,25 @@ function useGlobalDarkMode() {
2247
2297
  );
2248
2298
  }
2249
2299
 
2300
+ // src/useGlobalWidth.ts
2301
+ var import_react2 = __toESM(require("react"));
2302
+ var useSyncExternalStore2 = import_react2.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
2303
+ const [, forceUpdate] = import_react2.default.useReducer((n) => n + 1, 0);
2304
+ import_react2.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
2305
+ return getSnapshot();
2306
+ };
2307
+ var NOOP_SUB = (_) => () => {
2308
+ };
2309
+ var ZERO_SNAP = () => 0;
2310
+ var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
2311
+ function useConditionalWidth(active) {
2312
+ return useSyncExternalStore2(
2313
+ active ? subscribeGlobalWidth : NOOP_SUB,
2314
+ active ? getGlobalWidth : ZERO_SNAP,
2315
+ ZERO_SNAP
2316
+ );
2317
+ }
2318
+
2250
2319
  // src/InteractiveWrapper.tsx
2251
2320
  function chain(original, extra) {
2252
2321
  return (...args) => {
@@ -2254,7 +2323,17 @@ function chain(original, extra) {
2254
2323
  extra();
2255
2324
  };
2256
2325
  }
2257
- var InteractiveWrapper = (0, import_react2.forwardRef)(
2326
+ var RESPONSIVE_MODS2 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2327
+ function hasResponsiveBuckets(resolved) {
2328
+ for (const key of Object.keys(resolved)) {
2329
+ if (key === "base") continue;
2330
+ for (const mod of key.split(":")) {
2331
+ if (RESPONSIVE_MODS2.has(mod)) return true;
2332
+ }
2333
+ }
2334
+ return false;
2335
+ }
2336
+ var InteractiveWrapper = (0, import_react3.forwardRef)(
2258
2337
  function InteractiveWrapper2({
2259
2338
  Component,
2260
2339
  resolvedStyle,
@@ -2269,18 +2348,22 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
2269
2348
  ...rest
2270
2349
  }, ref) {
2271
2350
  const isDark = useGlobalDarkMode();
2272
- const [pressed, setPressed] = (0, import_react2.useState)(false);
2273
- const [hovered, setHovered] = (0, import_react2.useState)(false);
2274
- const [focused, setFocused] = (0, import_react2.useState)(false);
2275
- const handlePressIn = (0, import_react2.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2276
- const handlePressOut = (0, import_react2.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2277
- const handleMouseEnter = (0, import_react2.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2278
- const handleMouseLeave = (0, import_react2.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2279
- const handleFocus = (0, import_react2.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2280
- const handleBlur = (0, import_react2.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2281
- const computedStyle = (0, import_react2.useMemo)(
2282
- () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }),
2283
- [resolvedStyle, isDark, pressed, hovered, focused]
2351
+ const needsWidth = !isWeb && hasResponsiveBuckets(resolvedStyle);
2352
+ const width = useConditionalWidth(needsWidth);
2353
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
2354
+ const [pressed, setPressed] = (0, import_react3.useState)(false);
2355
+ const [hovered, setHovered] = (0, import_react3.useState)(false);
2356
+ const [focused, setFocused] = (0, import_react3.useState)(false);
2357
+ const handlePressIn = (0, import_react3.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2358
+ const handlePressOut = (0, import_react3.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2359
+ const handleMouseEnter = (0, import_react3.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2360
+ const handleMouseLeave = (0, import_react3.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2361
+ const handleFocus = (0, import_react3.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2362
+ const handleBlur = (0, import_react3.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2363
+ const computedStyle = (0, import_react3.useMemo)(
2364
+ () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }, breakpoints),
2365
+ [resolvedStyle, isDark, pressed, hovered, focused, width]
2366
+ // eslint-disable-line react-hooks/exhaustive-deps
2284
2367
  );
2285
2368
  const finalStyle = styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
2286
2369
  const { children, ...restWithoutChildren } = rest;
@@ -2296,33 +2379,88 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
2296
2379
  onFocus: handleFocus,
2297
2380
  onBlur: handleBlur
2298
2381
  };
2299
- return Array.isArray(children) ? import_react2.default.createElement(Component, componentProps, ...children) : import_react2.default.createElement(Component, componentProps, children);
2382
+ return Array.isArray(children) ? import_react3.default.createElement(Component, componentProps, ...children) : import_react3.default.createElement(Component, componentProps, children);
2300
2383
  }
2301
2384
  );
2302
2385
  InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
2303
2386
 
2304
2387
  // src/DarkWrapper.tsx
2305
- var import_react3 = __toESM(require("react"));
2306
- var DarkWrapper = import_react3.default.forwardRef(
2388
+ var import_react4 = __toESM(require("react"));
2389
+ var RESPONSIVE_MODS3 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2390
+ function hasResponsiveBuckets2(resolved) {
2391
+ for (const key of Object.keys(resolved)) {
2392
+ if (key === "base") continue;
2393
+ for (const mod of key.split(":")) {
2394
+ if (RESPONSIVE_MODS3.has(mod)) return true;
2395
+ }
2396
+ }
2397
+ return false;
2398
+ }
2399
+ var DarkWrapper = import_react4.default.forwardRef(
2307
2400
  function DarkWrapper2({ Component, resolvedStyle, style: styleProp, children, ...rest }, ref) {
2308
2401
  const isDark = useGlobalDarkMode();
2309
- const computedStyle = flatten(resolvedStyle, isDark);
2402
+ const needsWidth = !isWeb && hasResponsiveBuckets2(resolvedStyle);
2403
+ const width = useConditionalWidth(needsWidth);
2404
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
2405
+ const computedStyle = flatten(resolvedStyle, isDark, {}, breakpoints);
2310
2406
  const finalStyle = styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
2311
2407
  const props = { ref, ...rest, style: finalStyle };
2312
- return Array.isArray(children) ? import_react3.default.createElement(Component, props, ...children) : import_react3.default.createElement(Component, props, children);
2408
+ return Array.isArray(children) ? import_react4.default.createElement(Component, props, ...children) : import_react4.default.createElement(Component, props, children);
2313
2409
  }
2314
2410
  );
2315
2411
  DarkWrapper.displayName = "Kbach.DarkWrapper";
2316
2412
 
2317
2413
  // src/jsx-runtime.tsx
2318
- var MODE_BUCKET_RE = /^(not-)?(dark|light)(:(not-)?(dark|light))*$/;
2319
- function hasInteractiveBuckets(resolved) {
2320
- return Object.keys(resolved).some((key) => {
2321
- if (key === "base") return false;
2322
- if (/^(sm|md|lg|xl|2xl)$/.test(key)) return false;
2323
- if (MODE_BUCKET_RE.test(key)) return false;
2324
- return true;
2325
- });
2414
+ var INTERACTIVE_MODS = /* @__PURE__ */ new Set([
2415
+ "hover",
2416
+ "not-hover",
2417
+ "focus",
2418
+ "not-focus",
2419
+ "active",
2420
+ "not-active",
2421
+ "pressed",
2422
+ "not-pressed",
2423
+ "disabled",
2424
+ "not-disabled",
2425
+ "checked",
2426
+ "not-checked",
2427
+ "visited",
2428
+ "not-visited",
2429
+ "placeholder",
2430
+ "focus-within",
2431
+ "focus-visible",
2432
+ "group-hover",
2433
+ "group-focus",
2434
+ "peer-hover",
2435
+ "peer-focus",
2436
+ "first",
2437
+ "last",
2438
+ "odd",
2439
+ "even",
2440
+ "only"
2441
+ ]);
2442
+ var RESPONSIVE_MODS4 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2443
+ var MODE_MODS_JSX = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
2444
+ function bucketMods(resolved) {
2445
+ let interactive = false;
2446
+ let modeOrResponsive = false;
2447
+ for (const key of Object.keys(resolved)) {
2448
+ if (key === "base") continue;
2449
+ const parts = key.split(":");
2450
+ for (const mod of parts) {
2451
+ if (INTERACTIVE_MODS.has(mod)) {
2452
+ interactive = true;
2453
+ break;
2454
+ }
2455
+ }
2456
+ for (const mod of parts) {
2457
+ if (MODE_MODS_JSX.has(mod) || RESPONSIVE_MODS4.has(mod)) {
2458
+ modeOrResponsive = true;
2459
+ break;
2460
+ }
2461
+ }
2462
+ }
2463
+ return { interactive, modeOrResponsive };
2326
2464
  }
2327
2465
  var CONSUMED_PROPS = /* @__PURE__ */ new Set(["className", "kb", "__kbachStyles", "__kbachClasses"]);
2328
2466
  function omitConsumed(props) {
@@ -2350,7 +2488,8 @@ function processElement(type, rawProps, key, isStaticChildren) {
2350
2488
  const config = getConfig();
2351
2489
  const resolved = __kbachStyles ?? (classStr ? resolve(classStr, config.theme, config.darkMode) : {});
2352
2490
  const { style: userStyle, ...passProps } = omitConsumed(rawProps);
2353
- if (hasInteractiveBuckets(resolved)) {
2491
+ const { interactive, modeOrResponsive } = bucketMods(resolved);
2492
+ if (interactive) {
2354
2493
  return (0, import_jsx_runtime.jsx)(InteractiveWrapper, {
2355
2494
  Component: type,
2356
2495
  resolvedStyle: resolved,
@@ -2359,10 +2498,7 @@ function processElement(type, rawProps, key, isStaticChildren) {
2359
2498
  ...passProps
2360
2499
  }, key);
2361
2500
  }
2362
- const hasModeVariants = Object.keys(resolved).some(
2363
- (k) => k !== "base" && MODE_BUCKET_RE.test(k)
2364
- );
2365
- if (hasModeVariants) {
2501
+ if (modeOrResponsive) {
2366
2502
  return (0, import_jsx_runtime.jsx)(DarkWrapper, {
2367
2503
  Component: type,
2368
2504
  resolvedStyle: resolved,
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-J322Q3UR.mjs";
6
- import "./chunk-ADRZPFQC.mjs";
5
+ } from "./chunk-ALKTUEAA.mjs";
6
+ import "./chunk-UFSME6UC.mjs";
7
7
  export {
8
8
  Fragment,
9
9
  jsx,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",