@kbach/react 0.6.25 → 0.6.26

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.
package/dist/index.mjs CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  useGlobalDarkMode,
43
43
  useGlobalWidth,
44
44
  useSyncExternalStore
45
- } from "./chunk-XISB5W3A.mjs";
45
+ } from "./chunk-AKX6I7WP.mjs";
46
46
 
47
47
  // src/context.tsx
48
48
  import { createContext, useContext } from "react";
@@ -118,12 +118,22 @@ function useColors() {
118
118
  return useMemo(() => wrapColors(config.theme.colors), [config.theme.colors]);
119
119
  }
120
120
 
121
+ // src/useSpacing.ts
122
+ import { useMemo as useMemo2 } from "react";
123
+ function wrapSpacing(rawSpacing) {
124
+ return rawSpacing;
125
+ }
126
+ function useSpacing() {
127
+ const { config } = useTheme();
128
+ return useMemo2(() => wrapSpacing(config.theme.spacing), [config.theme.spacing]);
129
+ }
130
+
121
131
  // src/ThemeProvider.tsx
122
132
  import {
123
133
  useCallback,
124
134
  useEffect,
125
135
  useLayoutEffect,
126
- useMemo as useMemo2,
136
+ useMemo as useMemo3,
127
137
  useRef,
128
138
  useState
129
139
  } from "react";
@@ -132,6 +142,8 @@ var useIsomorphicLayoutEffect = isWeb || isNative ? useLayoutEffect : useEffect;
132
142
  var STORAGE_KEY = "kbach-theme";
133
143
  var _mountedProviderCount = 0;
134
144
  var _warnedMultipleProviders = false;
145
+ var _mountedConfigOverrideCount = 0;
146
+ var _warnedConfigOverride = false;
135
147
  function loadPersistedMode() {
136
148
  try {
137
149
  if (isWeb) return localStorage.getItem(STORAGE_KEY);
@@ -183,21 +195,38 @@ function ThemeProvider({
183
195
  const [resolvedConfig, setResolvedConfig] = useState(
184
196
  () => configOverride ? buildConfig(configOverride) : getConfig()
185
197
  );
198
+ const _prevConfigOverrideRef = useRef(void 0);
199
+ if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
200
+ _prevConfigOverrideRef.current = configOverride;
201
+ updateConfig(configOverride);
202
+ const fresh = getConfig();
203
+ if (fresh !== resolvedConfig) setResolvedConfig(fresh);
204
+ }
186
205
  useEffect(() => {
187
206
  _mountedProviderCount++;
188
- if (process.env.NODE_ENV !== "production" && _mountedProviderCount > 1 && !_warnedMultipleProviders) {
189
- _warnedMultipleProviders = true;
190
- kbachWarn(
191
- "Multiple <ThemeProvider> instances are mounted at once. Dark mode and responsive width are shared through one global store, so whichever provider rendered most recently wins for every consumer \u2014 nested or per-section theming is not isolated between providers."
192
- );
207
+ if (configOverride) _mountedConfigOverrideCount++;
208
+ if (process.env.NODE_ENV !== "production") {
209
+ if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
210
+ _warnedMultipleProviders = true;
211
+ kbachWarn(
212
+ "Multiple <ThemeProvider> instances are mounted at once. Dark mode and responsive width are shared through one global store, so whichever provider rendered most recently wins for every consumer \u2014 nested or per-section theming is not isolated between providers."
213
+ );
214
+ }
215
+ if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
216
+ _warnedConfigOverride = true;
217
+ kbachWarn(
218
+ "Multiple <ThemeProvider config={...}> overrides are mounted at once. The resolved config is shared through the same global store as dark mode/width (see the multiple-providers warning), so per-tree config overrides are not actually isolated between providers \u2014 plain className/kb elements with no hover:/dark:/sm: modifier resolve against whichever override last committed, not necessarily their nearest ancestor <ThemeProvider>."
219
+ );
220
+ }
193
221
  }
194
222
  return () => {
195
223
  _mountedProviderCount--;
224
+ if (configOverride) _mountedConfigOverrideCount--;
196
225
  };
197
226
  }, []);
198
227
  const [webWidth, setWebWidth] = useState(0);
199
228
  const effectiveWidth = windowWidthProp ?? (isWeb ? webWidth : 0);
200
- const numericScreens = useMemo2(() => {
229
+ const numericScreens = useMemo3(() => {
201
230
  const out = {};
202
231
  for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
203
232
  out[k] = typeof v === "number" ? v : parseInt(String(v), 10);
@@ -272,11 +301,6 @@ function ThemeProvider({
272
301
  applyWebTheme(resolvedMode, resolvedConfig.darkMode);
273
302
  setGlobalDarkMode(isDark);
274
303
  }, [isDark, resolvedMode, resolvedConfig.darkMode]);
275
- useEffect(() => {
276
- if (!configOverride) return;
277
- updateConfig(configOverride);
278
- setResolvedConfig(getConfig());
279
- }, [configOverride]);
280
304
  useEffect(() => {
281
305
  if (configOverride) return;
282
306
  let mounted = true;
@@ -288,7 +312,7 @@ function ThemeProvider({
288
312
  unsub();
289
313
  };
290
314
  }, [configOverride]);
291
- const contextValue = useMemo2(
315
+ const contextValue = useMemo3(
292
316
  () => ({ mode, resolvedMode, isDark, setMode, toggle, config: resolvedConfig }),
293
317
  [mode, resolvedMode, isDark, setMode, toggle, resolvedConfig]
294
318
  );
@@ -307,7 +331,7 @@ import React2, {
307
331
  forwardRef,
308
332
  useState as useState2,
309
333
  useCallback as useCallback2,
310
- useMemo as useMemo3
334
+ useMemo as useMemo4
311
335
  } from "react";
312
336
  function styled(Component, baseClasses = "") {
313
337
  const Styled = forwardRef(
@@ -366,12 +390,12 @@ function styled(Component, baseClasses = "") {
366
390
  if (!isWebPlatform) setFocused(false);
367
391
  }), [onBlur, isWebPlatform]);
368
392
  const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
369
- const resolved = useMemo3(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
370
- const hasInteractive = useMemo3(() => hasInteractiveBuckets(resolved), [resolved]);
393
+ const resolved = useMemo4(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
394
+ const hasInteractive = useMemo4(() => hasInteractiveBuckets(resolved), [resolved]);
371
395
  const needsWidth = hasResponsiveBuckets(resolved);
372
396
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
373
397
  const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
374
- const computedStyle = useMemo3(
398
+ const computedStyle = useMemo4(
375
399
  () => isWebPlatform ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
376
400
  [resolved, isDark, pressed, hovered, focused, disabled, checked, width]
377
401
  // eslint-disable-line react-hooks/exhaustive-deps
@@ -426,12 +450,12 @@ function styled(Component, baseClasses = "") {
426
450
  }
427
451
 
428
452
  // src/useStyles.ts
429
- import { useMemo as useMemo4 } from "react";
453
+ import { useMemo as useMemo5 } from "react";
430
454
  function useStyles(classString, state = {}) {
431
455
  const { isDark, config } = useTheme();
432
456
  const width = useGlobalWidth();
433
457
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
434
- return useMemo4(() => {
458
+ return useMemo5(() => {
435
459
  const resolved = resolve(normalised, config.theme, config.darkMode);
436
460
  const breakpoints = getActiveBreakpoints(width);
437
461
  return flatten(resolved, isDark, state, breakpoints);
@@ -440,39 +464,54 @@ function useStyles(classString, state = {}) {
440
464
  function useResolvedStyle(classString) {
441
465
  const { config } = useTheme();
442
466
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
443
- return useMemo4(
467
+ return useMemo5(
444
468
  () => resolve(normalised, config.theme, config.darkMode),
445
469
  [normalised, config]
446
470
  );
447
471
  }
448
472
 
449
473
  // src/useBreakpoint.ts
450
- import { useMemo as useMemo5 } from "react";
474
+ import { useMemo as useMemo6 } from "react";
475
+ function toNumericScreens(screens) {
476
+ const out = {};
477
+ for (const [name, v] of Object.entries(screens)) {
478
+ const n = typeof v === "number" ? v : parseInt(String(v), 10);
479
+ if (!Number.isNaN(n)) out[name] = n;
480
+ }
481
+ return out;
482
+ }
451
483
  function useBreakpoint() {
452
484
  const { config } = useTheme();
453
485
  const width = useGlobalWidth();
454
486
  const screens = config.theme.screens;
455
- const sorted = useMemo5(
456
- () => Object.entries(screens).map(([name, v]) => [name, typeof v === "number" ? v : parseInt(String(v), 10)]).filter(([, minW]) => !Number.isNaN(minW)).sort(([, a], [, b]) => b - a),
457
- [screens]
458
- );
459
- for (const [name, minW] of sorted) {
460
- if (width >= minW) return name;
461
- }
462
- return "xs";
487
+ const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
488
+ return useMemo6(() => {
489
+ const active = getActiveBreakpoints(width, numericScreens);
490
+ let best = null;
491
+ let bestMinW = -Infinity;
492
+ for (const name of active) {
493
+ const minW = numericScreens[name];
494
+ if (minW > bestMinW) {
495
+ bestMinW = minW;
496
+ best = name;
497
+ }
498
+ }
499
+ return best ?? "xs";
500
+ }, [numericScreens, width]);
463
501
  }
464
502
  function useResponsive() {
465
503
  const { config } = useTheme();
466
504
  const width = useGlobalWidth();
467
505
  const screens = config.theme.screens;
468
- return useMemo5(() => {
506
+ const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
507
+ return useMemo6(() => {
508
+ const active = getActiveBreakpoints(width, numericScreens);
469
509
  const result = {};
470
- for (const [name, v] of Object.entries(screens)) {
471
- const minW = typeof v === "number" ? v : parseInt(String(v), 10);
472
- if (!Number.isNaN(minW)) result[name] = width >= minW;
510
+ for (const name of Object.keys(numericScreens)) {
511
+ result[name] = active.has(name);
473
512
  }
474
513
  return result;
475
- }, [screens, width]);
514
+ }, [numericScreens, width]);
476
515
  }
477
516
 
478
517
  // src/kb.ts
@@ -517,7 +556,9 @@ export {
517
556
  useIsDark,
518
557
  useResolvedStyle,
519
558
  useResponsive,
559
+ useSpacing,
520
560
  useStyles,
521
561
  useTheme,
522
- wrapColors
562
+ wrapColors,
563
+ wrapSpacing
523
564
  };