@kbach/react 0.6.39 → 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.
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  buildConfig,
8
8
  chain,
9
9
  clearCache,
10
+ composeNativeStyle,
10
11
  defaultColors,
11
12
  defaultTheme,
12
13
  disableRuntimeCSS,
@@ -15,6 +16,7 @@ import {
15
16
  getActiveBreakpoints,
16
17
  getConfig,
17
18
  getEffectiveIsWeb,
19
+ getGlobalScreens,
18
20
  getImpliedRNStyle,
19
21
  getWebTag,
20
22
  hasInteractiveBuckets,
@@ -30,6 +32,7 @@ import {
30
32
  parseClasses,
31
33
  parseHexRgb,
32
34
  registerWebElement,
35
+ resetConfig,
33
36
  resolve,
34
37
  setGlobalDarkMode,
35
38
  setGlobalWidth,
@@ -45,7 +48,7 @@ import {
45
48
  useGlobalDarkMode,
46
49
  useGlobalWidth,
47
50
  useSyncExternalStore
48
- } from "./chunk-NE4LAJXK.mjs";
51
+ } from "./chunk-LQH3CJ3G.mjs";
49
52
 
50
53
  // src/context.tsx
51
54
  import { createContext, useContext } from "react";
@@ -152,6 +155,7 @@ var _mountedProviderCount = 0;
152
155
  var _warnedMultipleProviders = false;
153
156
  var _mountedConfigOverrideCount = 0;
154
157
  var _warnedConfigOverride = false;
158
+ var _warnedMediaModeToggle = false;
155
159
  function loadPersistedMode() {
156
160
  try {
157
161
  if (isWeb) return localStorage.getItem(STORAGE_KEY);
@@ -230,6 +234,9 @@ function ThemeProvider({
230
234
  return () => {
231
235
  _mountedProviderCount--;
232
236
  if (configOverride) _mountedConfigOverrideCount--;
237
+ if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
238
+ resetConfig();
239
+ }
233
240
  };
234
241
  }, []);
235
242
  const [webWidth, setWebWidth] = useState(0);
@@ -296,9 +303,15 @@ function ThemeProvider({
296
303
  return () => window.removeEventListener("storage", onStorage);
297
304
  }, [disablePersistence]);
298
305
  const setMode = useCallback((next) => {
306
+ if (process.env.NODE_ENV !== "production" && isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
307
+ _warnedMediaModeToggle = true;
308
+ kbachWarn(
309
+ 'setMode()/toggle() was called while darkMode is "media" \u2014 the resolved mode/isDark from useTheme()/useIsDark() will update, but the actual CSS stays driven by prefers-color-scheme and will not change to match. Use darkMode: "attribute" or "class" if the app needs manual toggling.'
310
+ );
311
+ }
299
312
  _setMode(next);
300
313
  if (!disablePersistence) persistMode(next);
301
- }, [disablePersistence]);
314
+ }, [disablePersistence, resolvedConfig.darkMode]);
302
315
  const toggle = useCallback(() => {
303
316
  setMode(mode === "dark" || mode === "system" && systemScheme === "dark" ? "light" : "dark");
304
317
  }, [mode, systemScheme, setMode]);
@@ -403,9 +416,10 @@ function styled(Component, baseClasses = "") {
403
416
  const needsWidth = hasResponsiveBuckets(resolved);
404
417
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
405
418
  const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
419
+ const screens = getGlobalScreens();
406
420
  const computedStyle = useMemo4(
407
421
  () => isWebPlatform ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
408
- [resolved, isDark, pressed, hovered, focused, disabled, checked, width]
422
+ [resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
409
423
  // eslint-disable-line react-hooks/exhaustive-deps
410
424
  );
411
425
  const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
@@ -414,7 +428,7 @@ function styled(Component, baseClasses = "") {
414
428
  const compensation = getImpliedRNStyle(webTag, resolved.base);
415
429
  if (!compensation) return flatStyleProp;
416
430
  return flatStyleProp ? { ...compensation, ...flatStyleProp } : compensation;
417
- })() : styleProp ? Array.isArray(styleProp) ? [computedStyle, ...styleProp] : [computedStyle, styleProp] : computedStyle;
431
+ })() : composeNativeStyle(computedStyle, styleProp);
418
432
  const effectiveComponent = webTag ?? Component;
419
433
  const componentName2 = Component.displayName ?? Component.name ?? "";
420
434
  const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
@@ -163,6 +163,10 @@ function stripWebOnlyProps(s) {
163
163
  delete s.position;
164
164
  }
165
165
  }
166
+ function composeNativeStyle(computed, userStyle) {
167
+ if (!userStyle) return computed;
168
+ return Array.isArray(userStyle) ? [computed, ...userStyle] : [computed, userStyle];
169
+ }
166
170
 
167
171
  // src/InteractiveWrapper.tsx
168
172
  var InteractiveWrapper = (0, import_react2.forwardRef)(
@@ -188,6 +192,7 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
188
192
  const needsWidth = hasResponsiveBuckets(resolvedStyle);
189
193
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
190
194
  const breakpoints = needsWidth ? (0, import_core4.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
195
+ const screens = (0, import_core4.getGlobalScreens)();
191
196
  const [pressed, setPressed] = (0, import_react2.useState)(false);
192
197
  const [hovered, setHovered] = (0, import_react2.useState)(false);
193
198
  const [focused, setFocused] = (0, import_react2.useState)(false);
@@ -231,10 +236,10 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
231
236
  if (isNonStringComponent) stripWebOnlyProps(s);
232
237
  return s;
233
238
  },
234
- [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked, isNonStringComponent]
239
+ [resolvedStyle, isDark, pressed, hovered, focused, width, screens, disabled, checked, isNonStringComponent]
235
240
  );
236
241
  const skipComputedInline = isWebPlatform;
237
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? [computedStyle, ...styleProp] : [computedStyle, styleProp] : computedStyle;
242
+ const finalStyle = skipComputedInline ? styleProp ?? void 0 : composeNativeStyle(computedStyle, styleProp);
238
243
  const componentProps = {
239
244
  ref,
240
245
  ...restForComponent,
@@ -291,7 +296,7 @@ var DarkWrapper = import_react3.default.forwardRef(
291
296
  return s;
292
297
  }, [resolvedStyle, isDark, width, screens, isNonStringComponent]);
293
298
  const skipComputedInline = isWebPlatform;
294
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? [computedStyle, ...styleProp] : [computedStyle, styleProp] : computedStyle;
299
+ const finalStyle = skipComputedInline ? styleProp ?? void 0 : composeNativeStyle(computedStyle, styleProp);
295
300
  const props = { ref, ...rest, style: finalStyle };
296
301
  return Array.isArray(children) ? import_react3.default.createElement(Component, props, ...children) : import_react3.default.createElement(Component, props, children);
297
302
  }
@@ -403,8 +408,9 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
403
408
  "lineBreakStrategyIOS",
404
409
  // TextInput
405
410
  "multiline",
406
- "resizeMode",
407
- // handled separately
411
+ // NOTE: 'resizeMode' (Image's prop, not TextInput's) deliberately does NOT
412
+ // go here — it's handled below in the isImage branch (mapped to CSS
413
+ // object-fit). Blacklisting it here would make that branch unreachable.
408
414
  "blurOnSubmit",
409
415
  "clearButtonMode",
410
416
  "clearTextOnFocus",
@@ -530,7 +536,7 @@ function transformToWebProps(originalName, tag, props) {
530
536
  continue;
531
537
  }
532
538
  if (k === "keyboardType") {
533
- if (!("type" in props) && !("secureTextEntry" in props)) {
539
+ if (!("type" in props) && !props.secureTextEntry) {
534
540
  const mapped = _keyboardTypeMap[v];
535
541
  if (mapped) out.type = mapped;
536
542
  }
@@ -546,7 +552,7 @@ function transformToWebProps(originalName, tag, props) {
546
552
  }
547
553
  }
548
554
  if (isScrollable && k === "horizontal") {
549
- if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
555
+ if (v) pendingStyle = { ...pendingStyle ?? {}, display: "flex", flexDirection: "row", overflowX: "auto" };
550
556
  continue;
551
557
  }
552
558
  if (k === "pointerEvents") {
@@ -640,10 +646,6 @@ function omitConsumed(props) {
640
646
  }
641
647
  return out;
642
648
  }
643
- function mergeStyle(computed, userStyle) {
644
- if (!userStyle) return computed;
645
- return Array.isArray(userStyle) ? [computed, ...userStyle] : [computed, userStyle];
646
- }
647
649
  function withImpliedRNStyleIfNeeded(webTag, resolvedBase, userStyle) {
648
650
  const compensation = getImpliedRNStyle(webTag, resolvedBase);
649
651
  if (!compensation) return userStyle;
@@ -719,7 +721,7 @@ function processElement(type, rawProps, key, isStaticChildren) {
719
721
  const computedStyle = (0, import_core6.flatten)(resolved, false);
720
722
  stripInternalMarkers(computedStyle);
721
723
  if (typeof effectiveType !== "string") stripWebOnlyProps(computedStyle);
722
- finalStyle = mergeStyle(computedStyle, userStyle);
724
+ finalStyle = composeNativeStyle(computedStyle, userStyle);
723
725
  } else {
724
726
  finalStyle = userStyle ?? void 0;
725
727
  }
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-Z4ZOGRZG.mjs";
7
- import "./chunk-NE4LAJXK.mjs";
6
+ } from "./chunk-ZFVHIT3V.mjs";
7
+ import "./chunk-LQH3CJ3G.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -164,6 +164,10 @@ function stripWebOnlyProps(s) {
164
164
  delete s.position;
165
165
  }
166
166
  }
167
+ function composeNativeStyle(computed, userStyle) {
168
+ if (!userStyle) return computed;
169
+ return Array.isArray(userStyle) ? [computed, ...userStyle] : [computed, userStyle];
170
+ }
167
171
 
168
172
  // src/InteractiveWrapper.tsx
169
173
  var InteractiveWrapper = (0, import_react2.forwardRef)(
@@ -189,6 +193,7 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
189
193
  const needsWidth = hasResponsiveBuckets(resolvedStyle);
190
194
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
191
195
  const breakpoints = needsWidth ? (0, import_core4.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
196
+ const screens = (0, import_core4.getGlobalScreens)();
192
197
  const [pressed, setPressed] = (0, import_react2.useState)(false);
193
198
  const [hovered, setHovered] = (0, import_react2.useState)(false);
194
199
  const [focused, setFocused] = (0, import_react2.useState)(false);
@@ -232,10 +237,10 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
232
237
  if (isNonStringComponent) stripWebOnlyProps(s);
233
238
  return s;
234
239
  },
235
- [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked, isNonStringComponent]
240
+ [resolvedStyle, isDark, pressed, hovered, focused, width, screens, disabled, checked, isNonStringComponent]
236
241
  );
237
242
  const skipComputedInline = isWebPlatform;
238
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? [computedStyle, ...styleProp] : [computedStyle, styleProp] : computedStyle;
243
+ const finalStyle = skipComputedInline ? styleProp ?? void 0 : composeNativeStyle(computedStyle, styleProp);
239
244
  const componentProps = {
240
245
  ref,
241
246
  ...restForComponent,
@@ -292,7 +297,7 @@ var DarkWrapper = import_react3.default.forwardRef(
292
297
  return s;
293
298
  }, [resolvedStyle, isDark, width, screens, isNonStringComponent]);
294
299
  const skipComputedInline = isWebPlatform;
295
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? [computedStyle, ...styleProp] : [computedStyle, styleProp] : computedStyle;
300
+ const finalStyle = skipComputedInline ? styleProp ?? void 0 : composeNativeStyle(computedStyle, styleProp);
296
301
  const props = { ref, ...rest, style: finalStyle };
297
302
  return Array.isArray(children) ? import_react3.default.createElement(Component, props, ...children) : import_react3.default.createElement(Component, props, children);
298
303
  }
@@ -409,8 +414,9 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
409
414
  "lineBreakStrategyIOS",
410
415
  // TextInput
411
416
  "multiline",
412
- "resizeMode",
413
- // handled separately
417
+ // NOTE: 'resizeMode' (Image's prop, not TextInput's) deliberately does NOT
418
+ // go here — it's handled below in the isImage branch (mapped to CSS
419
+ // object-fit). Blacklisting it here would make that branch unreachable.
414
420
  "blurOnSubmit",
415
421
  "clearButtonMode",
416
422
  "clearTextOnFocus",
@@ -536,7 +542,7 @@ function transformToWebProps(originalName, tag, props) {
536
542
  continue;
537
543
  }
538
544
  if (k === "keyboardType") {
539
- if (!("type" in props) && !("secureTextEntry" in props)) {
545
+ if (!("type" in props) && !props.secureTextEntry) {
540
546
  const mapped = _keyboardTypeMap[v];
541
547
  if (mapped) out.type = mapped;
542
548
  }
@@ -552,7 +558,7 @@ function transformToWebProps(originalName, tag, props) {
552
558
  }
553
559
  }
554
560
  if (isScrollable && k === "horizontal") {
555
- if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
561
+ if (v) pendingStyle = { ...pendingStyle ?? {}, display: "flex", flexDirection: "row", overflowX: "auto" };
556
562
  continue;
557
563
  }
558
564
  if (k === "pointerEvents") {
@@ -646,10 +652,6 @@ function omitConsumed(props) {
646
652
  }
647
653
  return out;
648
654
  }
649
- function mergeStyle(computed, userStyle) {
650
- if (!userStyle) return computed;
651
- return Array.isArray(userStyle) ? [computed, ...userStyle] : [computed, userStyle];
652
- }
653
655
  function withImpliedRNStyleIfNeeded(webTag, resolvedBase, userStyle) {
654
656
  const compensation = getImpliedRNStyle(webTag, resolvedBase);
655
657
  if (!compensation) return userStyle;
@@ -725,7 +727,7 @@ function processElement(type, rawProps, key, isStaticChildren) {
725
727
  const computedStyle = (0, import_core6.flatten)(resolved, false);
726
728
  stripInternalMarkers(computedStyle);
727
729
  if (typeof effectiveType !== "string") stripWebOnlyProps(computedStyle);
728
- finalStyle = mergeStyle(computedStyle, userStyle);
730
+ finalStyle = composeNativeStyle(computedStyle, userStyle);
729
731
  } else {
730
732
  finalStyle = userStyle ?? void 0;
731
733
  }
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-Z4ZOGRZG.mjs";
7
+ } from "./chunk-ZFVHIT3V.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-NE4LAJXK.mjs";
10
+ } from "./chunk-LQH3CJ3G.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,
@@ -11,6 +11,22 @@ import { Plugin } from 'vite';
11
11
  * Plugin authors can register custom modifiers via registerModifier().
12
12
  */
13
13
  interface ModifierDef {
14
+ /**
15
+ * Cascade priority for CSS rule ORDER — NOT specificity. Two rules that
16
+ * differ only by modifier (e.g. `.hover\:bg-blue-6:hover` and
17
+ * `.focus\:bg-red-6:focus`) have equal CSS specificity, so when both
18
+ * conditions are true at once (hovering AND focused), the winner is
19
+ * whichever rule appears LATER in the stylesheet — CSS's normal same-
20
+ * specificity tiebreak. Without a fixed priority, "later" would depend on
21
+ * encounter order (whichever class the app happens to render/scan first),
22
+ * making the winner effectively random and inconsistent across reloads/
23
+ * builds. `order` fixes that: rules are emitted/injected sorted by this
24
+ * value (ascending — higher wins ties), regardless of source order, so
25
+ * e.g. `disabled:` always beats `hover:` on the same element no matter
26
+ * which one was written first in the className or rendered first in the
27
+ * app. Omit for the default (0). See getModifierOrder() below.
28
+ */
29
+ order?: number;
14
30
  /** CSS pseudo-class or pseudo-element appended to the selector (e.g. ':hover', '::before') */
15
31
  pseudo?: string;
16
32
  /** Ancestor selector prefix INCLUDING trailing space (e.g. '.group:hover ', '.peer:focus ~ ') */
@@ -128,6 +144,7 @@ interface FrameworkConfig {
128
144
  content?: string[];
129
145
  }
130
146
 
147
+ declare function formatKbachCSS(tokenCSS: Map<string, string>, theme: ThemeConfig, responsiveRe: RegExp): string;
131
148
  interface KbachPluginOptions {
132
149
  framework?: FrameworkConfig;
133
150
  /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
@@ -135,4 +152,4 @@ interface KbachPluginOptions {
135
152
  }
136
153
  declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
137
154
 
138
- export { type KbachPluginOptions, kbach };
155
+ export { type KbachPluginOptions, formatKbachCSS, kbach };
@@ -11,6 +11,22 @@ import { Plugin } from 'vite';
11
11
  * Plugin authors can register custom modifiers via registerModifier().
12
12
  */
13
13
  interface ModifierDef {
14
+ /**
15
+ * Cascade priority for CSS rule ORDER — NOT specificity. Two rules that
16
+ * differ only by modifier (e.g. `.hover\:bg-blue-6:hover` and
17
+ * `.focus\:bg-red-6:focus`) have equal CSS specificity, so when both
18
+ * conditions are true at once (hovering AND focused), the winner is
19
+ * whichever rule appears LATER in the stylesheet — CSS's normal same-
20
+ * specificity tiebreak. Without a fixed priority, "later" would depend on
21
+ * encounter order (whichever class the app happens to render/scan first),
22
+ * making the winner effectively random and inconsistent across reloads/
23
+ * builds. `order` fixes that: rules are emitted/injected sorted by this
24
+ * value (ascending — higher wins ties), regardless of source order, so
25
+ * e.g. `disabled:` always beats `hover:` on the same element no matter
26
+ * which one was written first in the className or rendered first in the
27
+ * app. Omit for the default (0). See getModifierOrder() below.
28
+ */
29
+ order?: number;
14
30
  /** CSS pseudo-class or pseudo-element appended to the selector (e.g. ':hover', '::before') */
15
31
  pseudo?: string;
16
32
  /** Ancestor selector prefix INCLUDING trailing space (e.g. '.group:hover ', '.peer:focus ~ ') */
@@ -128,6 +144,7 @@ interface FrameworkConfig {
128
144
  content?: string[];
129
145
  }
130
146
 
147
+ declare function formatKbachCSS(tokenCSS: Map<string, string>, theme: ThemeConfig, responsiveRe: RegExp): string;
131
148
  interface KbachPluginOptions {
132
149
  framework?: FrameworkConfig;
133
150
  /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
@@ -135,4 +152,4 @@ interface KbachPluginOptions {
135
152
  }
136
153
  declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
137
154
 
138
- export { type KbachPluginOptions, kbach };
155
+ export { type KbachPluginOptions, formatKbachCSS, kbach };