@kbach/react 0.3.3 → 0.3.5

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.
@@ -1870,6 +1870,36 @@ function getPluginStandaloneMap() {
1870
1870
  return PLUGIN_STANDALONE;
1871
1871
  }
1872
1872
 
1873
+ // src/core/responsiveStore.ts
1874
+ var RESPONSIVE_KEY = "__kbach_responsive__";
1875
+ function getStore() {
1876
+ const g = globalThis;
1877
+ if (!g[RESPONSIVE_KEY]) {
1878
+ g[RESPONSIVE_KEY] = { width: 0, screens: {}, listeners: /* @__PURE__ */ new Set() };
1879
+ }
1880
+ return g[RESPONSIVE_KEY];
1881
+ }
1882
+ function getGlobalScreens() {
1883
+ return getStore().screens;
1884
+ }
1885
+ function getGlobalWidth() {
1886
+ return getStore().width;
1887
+ }
1888
+ function subscribeGlobalWidth(listener) {
1889
+ const store = getStore();
1890
+ store.listeners.add(listener);
1891
+ return () => store.listeners.delete(listener);
1892
+ }
1893
+ function getActiveBreakpoints(width) {
1894
+ const store = getStore();
1895
+ const w = width ?? store.width;
1896
+ const active = /* @__PURE__ */ new Set();
1897
+ for (const [name, minW] of Object.entries(store.screens)) {
1898
+ if (typeof minW === "number" && w >= minW) active.add(name);
1899
+ }
1900
+ return active;
1901
+ }
1902
+
1873
1903
  // src/core/resolver.ts
1874
1904
  var styleCache = new LRUCache(1e4);
1875
1905
  var _styleEl = null;
@@ -1920,6 +1950,7 @@ var GROUP_PEER_PREFIX = {
1920
1950
  "peer-focus": ".peer:focus ~"
1921
1951
  };
1922
1952
  var MODE_MODS = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
1953
+ var RESPONSIVE_MODS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1923
1954
  function injectClassRule(cls, bucketKey, styles, darkMode) {
1924
1955
  const cssDecls = styleValueToCSS(styles);
1925
1956
  if (!cssDecls) return;
@@ -1929,10 +1960,12 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1929
1960
  return;
1930
1961
  }
1931
1962
  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));
1963
+ const responsiveMods = mods.filter((m) => RESPONSIVE_MODS.has(m));
1964
+ const nonResponsiveMods = mods.filter((m) => !RESPONSIVE_MODS.has(m));
1965
+ const hasDark = nonResponsiveMods.includes("dark") || nonResponsiveMods.includes("not-light");
1966
+ const hasLight = nonResponsiveMods.includes("light") || nonResponsiveMods.includes("not-dark");
1967
+ const ancestorMods = nonResponsiveMods.filter((m) => m in GROUP_PEER_PREFIX);
1968
+ const pseudoMods = nonResponsiveMods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
1936
1969
  const pseudoSuffix = pseudoMods.map((p) => MODIFIER_TO_PSEUDO[p] ?? `:${p}`).join("");
1937
1970
  const elementSelector = `.${escaped}${pseudoSuffix}`;
1938
1971
  const ancestorPrefix = ancestorMods.length ? ancestorMods.map((m) => GROUP_PEER_PREFIX[m]).join(" ") + " " : "";
@@ -1957,6 +1990,13 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1957
1990
  } else {
1958
1991
  rule = `${selector} { ${cssDecls} }`;
1959
1992
  }
1993
+ if (responsiveMods.length > 0) {
1994
+ const screens = getGlobalScreens();
1995
+ const minWidth = Math.max(...responsiveMods.map((m) => screens[m] ?? 0));
1996
+ if (minWidth > 0) {
1997
+ rule = `@media (min-width: ${minWidth}px) { ${rule} }`;
1998
+ }
1999
+ }
1960
2000
  injectRule(rule);
1961
2001
  }
1962
2002
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2025,18 +2065,18 @@ function resolve(classString, theme, darkMode = "attribute") {
2025
2065
  }
2026
2066
  return result;
2027
2067
  }
2028
- function flatten(resolved, isDark, state = {}) {
2068
+ function flatten(resolved, isDark, state = {}, breakpoints = /* @__PURE__ */ new Set()) {
2029
2069
  const result = { ...resolved.base ?? {} };
2030
2070
  for (const [key, styles] of Object.entries(resolved)) {
2031
2071
  if (key === "base" || !styles) continue;
2032
2072
  const mods = key.split(":");
2033
- if (matchesMods(mods, isDark, state)) {
2073
+ if (matchesMods(mods, isDark, state, breakpoints)) {
2034
2074
  Object.assign(result, styles);
2035
2075
  }
2036
2076
  }
2037
2077
  return result;
2038
2078
  }
2039
- function matchesMods(mods, isDark, state) {
2079
+ function matchesMods(mods, isDark, state, breakpoints) {
2040
2080
  return mods.every((mod) => {
2041
2081
  switch (mod) {
2042
2082
  case "dark":
@@ -2074,6 +2114,12 @@ function matchesMods(mods, isDark, state) {
2074
2114
  return !state.visited;
2075
2115
  case "placeholder":
2076
2116
  return !!state.placeholder;
2117
+ case "sm":
2118
+ case "md":
2119
+ case "lg":
2120
+ case "xl":
2121
+ case "2xl":
2122
+ return breakpoints.has(mod);
2077
2123
  default:
2078
2124
  return false;
2079
2125
  }
@@ -2111,6 +2157,28 @@ function getConfig() {
2111
2157
  store.resolved = buildConfig({});
2112
2158
  return store.resolved;
2113
2159
  }
2160
+ function resolveOneRef(ref, colors) {
2161
+ const m = /^([a-z][a-z0-9]*)-(\d+)$/.exec(ref);
2162
+ if (!m) return null;
2163
+ const entry = colors[m[1]];
2164
+ if (!entry || typeof entry !== "object") return null;
2165
+ return entry[m[2]] ?? null;
2166
+ }
2167
+ function resolveColorRefs(colors) {
2168
+ const out = {};
2169
+ for (const [key, val] of Object.entries(colors)) {
2170
+ if (typeof val === "string") {
2171
+ out[key] = resolveOneRef(val, colors) ?? val;
2172
+ } else {
2173
+ const shades = {};
2174
+ for (const [shade, hex] of Object.entries(val)) {
2175
+ shades[shade] = resolveOneRef(hex, colors) ?? hex;
2176
+ }
2177
+ out[key] = shades;
2178
+ }
2179
+ }
2180
+ return out;
2181
+ }
2114
2182
  function buildConfig(userConfig) {
2115
2183
  let theme = { ...defaultTheme };
2116
2184
  if (userConfig.theme) {
@@ -2129,6 +2197,7 @@ function buildConfig(userConfig) {
2129
2197
  }
2130
2198
  }
2131
2199
  }
2200
+ theme = { ...theme, colors: resolveColorRefs(theme.colors) };
2132
2201
  const resolved = {
2133
2202
  darkMode: userConfig.darkMode ?? "attribute",
2134
2203
  theme,
@@ -2189,7 +2258,7 @@ var customVariants = new Proxy(
2189
2258
 
2190
2259
  // src/core/darkModeStore.ts
2191
2260
  var KEY = "__kbach_dark_store__";
2192
- function getStore() {
2261
+ function getStore2() {
2193
2262
  const g = globalThis;
2194
2263
  if (!g[KEY]) {
2195
2264
  g[KEY] = { isDark: false, lastNotified: false, subscribers: /* @__PURE__ */ new Set() };
@@ -2197,16 +2266,16 @@ function getStore() {
2197
2266
  return g[KEY];
2198
2267
  }
2199
2268
  function getGlobalDarkMode() {
2200
- return getStore().isDark;
2269
+ return getStore2().isDark;
2201
2270
  }
2202
2271
  function subscribeGlobalDarkMode(callback) {
2203
- const store = getStore();
2272
+ const store = getStore2();
2204
2273
  store.subscribers.add(callback);
2205
2274
  return () => store.subscribers.delete(callback);
2206
2275
  }
2207
2276
 
2208
2277
  // src/InteractiveWrapper.tsx
2209
- var import_react2 = __toESM(require("react"));
2278
+ var import_react3 = __toESM(require("react"));
2210
2279
 
2211
2280
  // src/useGlobalDarkMode.ts
2212
2281
  var import_react = __toESM(require("react"));
@@ -2224,6 +2293,25 @@ function useGlobalDarkMode() {
2224
2293
  );
2225
2294
  }
2226
2295
 
2296
+ // src/useGlobalWidth.ts
2297
+ var import_react2 = __toESM(require("react"));
2298
+ var useSyncExternalStore2 = import_react2.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
2299
+ const [, forceUpdate] = import_react2.default.useReducer((n) => n + 1, 0);
2300
+ import_react2.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
2301
+ return getSnapshot();
2302
+ };
2303
+ var NOOP_SUB = (_) => () => {
2304
+ };
2305
+ var ZERO_SNAP = () => 0;
2306
+ var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
2307
+ function useConditionalWidth(active) {
2308
+ return useSyncExternalStore2(
2309
+ active ? subscribeGlobalWidth : NOOP_SUB,
2310
+ active ? getGlobalWidth : ZERO_SNAP,
2311
+ ZERO_SNAP
2312
+ );
2313
+ }
2314
+
2227
2315
  // src/InteractiveWrapper.tsx
2228
2316
  function chain(original, extra) {
2229
2317
  return (...args) => {
@@ -2231,7 +2319,17 @@ function chain(original, extra) {
2231
2319
  extra();
2232
2320
  };
2233
2321
  }
2234
- var InteractiveWrapper = (0, import_react2.forwardRef)(
2322
+ var RESPONSIVE_MODS2 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2323
+ function hasResponsiveBuckets(resolved) {
2324
+ for (const key of Object.keys(resolved)) {
2325
+ if (key === "base") continue;
2326
+ for (const mod of key.split(":")) {
2327
+ if (RESPONSIVE_MODS2.has(mod)) return true;
2328
+ }
2329
+ }
2330
+ return false;
2331
+ }
2332
+ var InteractiveWrapper = (0, import_react3.forwardRef)(
2235
2333
  function InteractiveWrapper2({
2236
2334
  Component,
2237
2335
  resolvedStyle,
@@ -2246,18 +2344,22 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
2246
2344
  ...rest
2247
2345
  }, ref) {
2248
2346
  const isDark = useGlobalDarkMode();
2249
- const [pressed, setPressed] = (0, import_react2.useState)(false);
2250
- const [hovered, setHovered] = (0, import_react2.useState)(false);
2251
- const [focused, setFocused] = (0, import_react2.useState)(false);
2252
- const handlePressIn = (0, import_react2.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2253
- const handlePressOut = (0, import_react2.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2254
- const handleMouseEnter = (0, import_react2.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2255
- const handleMouseLeave = (0, import_react2.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2256
- const handleFocus = (0, import_react2.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2257
- const handleBlur = (0, import_react2.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2258
- const computedStyle = (0, import_react2.useMemo)(
2259
- () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }),
2260
- [resolvedStyle, isDark, pressed, hovered, focused]
2347
+ const needsWidth = !isWeb && hasResponsiveBuckets(resolvedStyle);
2348
+ const width = useConditionalWidth(needsWidth);
2349
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
2350
+ const [pressed, setPressed] = (0, import_react3.useState)(false);
2351
+ const [hovered, setHovered] = (0, import_react3.useState)(false);
2352
+ const [focused, setFocused] = (0, import_react3.useState)(false);
2353
+ const handlePressIn = (0, import_react3.useCallback)(chain(onPressIn, () => setPressed(true)), [onPressIn]);
2354
+ const handlePressOut = (0, import_react3.useCallback)(chain(onPressOut, () => setPressed(false)), [onPressOut]);
2355
+ const handleMouseEnter = (0, import_react3.useCallback)(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
2356
+ const handleMouseLeave = (0, import_react3.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
2357
+ const handleFocus = (0, import_react3.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
2358
+ const handleBlur = (0, import_react3.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
2359
+ const computedStyle = (0, import_react3.useMemo)(
2360
+ () => flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused }, breakpoints),
2361
+ [resolvedStyle, isDark, pressed, hovered, focused, width]
2362
+ // eslint-disable-line react-hooks/exhaustive-deps
2261
2363
  );
2262
2364
  const finalStyle = styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
2263
2365
  const { children, ...restWithoutChildren } = rest;
@@ -2273,33 +2375,88 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
2273
2375
  onFocus: handleFocus,
2274
2376
  onBlur: handleBlur
2275
2377
  };
2276
- return Array.isArray(children) ? import_react2.default.createElement(Component, componentProps, ...children) : import_react2.default.createElement(Component, componentProps, children);
2378
+ return Array.isArray(children) ? import_react3.default.createElement(Component, componentProps, ...children) : import_react3.default.createElement(Component, componentProps, children);
2277
2379
  }
2278
2380
  );
2279
2381
  InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
2280
2382
 
2281
2383
  // src/DarkWrapper.tsx
2282
- var import_react3 = __toESM(require("react"));
2283
- var DarkWrapper = import_react3.default.forwardRef(
2384
+ var import_react4 = __toESM(require("react"));
2385
+ var RESPONSIVE_MODS3 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2386
+ function hasResponsiveBuckets2(resolved) {
2387
+ for (const key of Object.keys(resolved)) {
2388
+ if (key === "base") continue;
2389
+ for (const mod of key.split(":")) {
2390
+ if (RESPONSIVE_MODS3.has(mod)) return true;
2391
+ }
2392
+ }
2393
+ return false;
2394
+ }
2395
+ var DarkWrapper = import_react4.default.forwardRef(
2284
2396
  function DarkWrapper2({ Component, resolvedStyle, style: styleProp, children, ...rest }, ref) {
2285
2397
  const isDark = useGlobalDarkMode();
2286
- const computedStyle = flatten(resolvedStyle, isDark);
2398
+ const needsWidth = !isWeb && hasResponsiveBuckets2(resolvedStyle);
2399
+ const width = useConditionalWidth(needsWidth);
2400
+ const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
2401
+ const computedStyle = flatten(resolvedStyle, isDark, {}, breakpoints);
2287
2402
  const finalStyle = styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
2288
2403
  const props = { ref, ...rest, style: finalStyle };
2289
- return Array.isArray(children) ? import_react3.default.createElement(Component, props, ...children) : import_react3.default.createElement(Component, props, children);
2404
+ return Array.isArray(children) ? import_react4.default.createElement(Component, props, ...children) : import_react4.default.createElement(Component, props, children);
2290
2405
  }
2291
2406
  );
2292
2407
  DarkWrapper.displayName = "Kbach.DarkWrapper";
2293
2408
 
2294
2409
  // src/jsx-runtime.tsx
2295
- var MODE_BUCKET_RE = /^(not-)?(dark|light)(:(not-)?(dark|light))*$/;
2296
- function hasInteractiveBuckets(resolved) {
2297
- return Object.keys(resolved).some((key) => {
2298
- if (key === "base") return false;
2299
- if (/^(sm|md|lg|xl|2xl)$/.test(key)) return false;
2300
- if (MODE_BUCKET_RE.test(key)) return false;
2301
- return true;
2302
- });
2410
+ var INTERACTIVE_MODS = /* @__PURE__ */ new Set([
2411
+ "hover",
2412
+ "not-hover",
2413
+ "focus",
2414
+ "not-focus",
2415
+ "active",
2416
+ "not-active",
2417
+ "pressed",
2418
+ "not-pressed",
2419
+ "disabled",
2420
+ "not-disabled",
2421
+ "checked",
2422
+ "not-checked",
2423
+ "visited",
2424
+ "not-visited",
2425
+ "placeholder",
2426
+ "focus-within",
2427
+ "focus-visible",
2428
+ "group-hover",
2429
+ "group-focus",
2430
+ "peer-hover",
2431
+ "peer-focus",
2432
+ "first",
2433
+ "last",
2434
+ "odd",
2435
+ "even",
2436
+ "only"
2437
+ ]);
2438
+ var RESPONSIVE_MODS4 = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
2439
+ var MODE_MODS_JSX = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
2440
+ function bucketMods(resolved) {
2441
+ let interactive = false;
2442
+ let modeOrResponsive = false;
2443
+ for (const key of Object.keys(resolved)) {
2444
+ if (key === "base") continue;
2445
+ const parts = key.split(":");
2446
+ for (const mod of parts) {
2447
+ if (INTERACTIVE_MODS.has(mod)) {
2448
+ interactive = true;
2449
+ break;
2450
+ }
2451
+ }
2452
+ for (const mod of parts) {
2453
+ if (MODE_MODS_JSX.has(mod) || RESPONSIVE_MODS4.has(mod)) {
2454
+ modeOrResponsive = true;
2455
+ break;
2456
+ }
2457
+ }
2458
+ }
2459
+ return { interactive, modeOrResponsive };
2303
2460
  }
2304
2461
  var CONSUMED_PROPS = /* @__PURE__ */ new Set(["className", "kb", "__kbachStyles", "__kbachClasses"]);
2305
2462
  function omitConsumed(props) {
@@ -2327,7 +2484,8 @@ function processElement(type, rawProps, key, isStaticChildren) {
2327
2484
  const config = getConfig();
2328
2485
  const resolved = __kbachStyles ?? (classStr ? resolve(classStr, config.theme, config.darkMode) : {});
2329
2486
  const { style: userStyle, ...passProps } = omitConsumed(rawProps);
2330
- if (hasInteractiveBuckets(resolved)) {
2487
+ const { interactive, modeOrResponsive } = bucketMods(resolved);
2488
+ if (interactive) {
2331
2489
  return (0, import_jsx_runtime.jsx)(InteractiveWrapper, {
2332
2490
  Component: type,
2333
2491
  resolvedStyle: resolved,
@@ -2336,10 +2494,7 @@ function processElement(type, rawProps, key, isStaticChildren) {
2336
2494
  ...passProps
2337
2495
  }, key);
2338
2496
  }
2339
- const hasModeVariants = Object.keys(resolved).some(
2340
- (k) => k !== "base" && MODE_BUCKET_RE.test(k)
2341
- );
2342
- if (hasModeVariants) {
2497
+ if (modeOrResponsive) {
2343
2498
  return (0, import_jsx_runtime.jsx)(DarkWrapper, {
2344
2499
  Component: type,
2345
2500
  resolvedStyle: resolved,
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-NFPYOE5K.mjs";
6
- import "./chunk-7XL6VQDW.mjs";
5
+ } from "./chunk-2WECG35P.mjs";
6
+ import "./chunk-6YT4ECUI.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.3",
3
+ "version": "0.3.5",
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",