@kbach/react 0.6.48 → 0.6.50

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/README.md CHANGED
@@ -39,6 +39,7 @@ That's the only setting needed — Vite, Next.js, and React Router all read it.
39
39
  | Vite, React Router library mode, CRA, other Vite-based | **[Static CSS setup](#static-css-setup)** (recommended) — zero runtime cost, catches typos at build time. [Runtime setup](#runtime-setup) is there if you'd rather skip the plugin for now. |
40
40
  | React Router, framework mode | **[Static CSS setup](#static-css-setup)** — and skip `@vitejs/plugin-react`, see note in that section |
41
41
  | Next.js | **[Runtime setup](#runtime-setup)** — Static CSS doesn't apply (webpack/Turbopack, not Vite) |
42
+ | React Native, Expo | **[React Native / Expo setup](#react-native--expo-setup)** — different setup entirely (Babel preset, not the JSX runtime step above) |
42
43
 
43
44
  ## Static CSS setup
44
45
 
@@ -119,6 +120,81 @@ Using a custom `kbach.config.js`? Pass it to `ThemeProvider` too — see [Wiring
119
120
 
120
121
  Don't also set up Static CSS above in the same app — pick one.
121
122
 
123
+ ## React Native / Expo setup
124
+
125
+ Same `npm install @kbach/react` — no separate package. Everything below (API, modifiers, color system) is the same import as web; only setup differs.
126
+
127
+ **1. babel.config.js:**
128
+
129
+ ```js
130
+ module.exports = function (api) {
131
+ api.cache(true);
132
+ return {
133
+ presets: [
134
+ 'babel-preset-expo',
135
+ '@kbach/react/babel',
136
+ ],
137
+ };
138
+ };
139
+ ```
140
+
141
+ Or the one-liner helper: `const { createKbachConfig } = require('@kbach/react/native'); module.exports = createKbachConfig();` — identical result. Merging into an existing config: `withKbachBabel({ presets: [...] })`, also from `@kbach/react/native`. After changing this file, clear the Metro cache: `npx expo start --clear`.
142
+
143
+ **2. Wrap your app:**
144
+
145
+ ```jsx
146
+ import { ThemeProvider } from '@kbach/react/native';
147
+
148
+ export default function App() {
149
+ return (
150
+ <ThemeProvider defaultMode="system">
151
+ <AppContent />
152
+ </ThemeProvider>
153
+ );
154
+ }
155
+ ```
156
+
157
+ This is a native-aware `ThemeProvider` — reads `useColorScheme()`/`useWindowDimensions()` automatically, no extra props needed. Import it from `@kbach/react/native`, not the plain `ThemeProvider` from `@kbach/react` — that one has no automatic RN wiring (colors scheme/window width would need to be passed in by hand).
158
+
159
+ ### Platform differences
160
+
161
+ A handful of utilities are native-only or web-only:
162
+
163
+ | | |
164
+ |---|---|
165
+ | Native-only | `tint-{color}` (Image/icon tinting), `perspective-{n}`, `backface-hidden`, `text-shadow`/`text-shadow-lg` |
166
+ | Web-only, ignored on native (no warning) | `caret-*` `accent-*` `stroke-*` `fill-*` `touch-*` `float-*` `clear-*` `line-clamp-*` `scroll-*` `animate-*` `transition` `filter` `backdrop-filter` `print:` `before:` `after:` `selection:` `first-letter:` `first-line:` `marker:` `landscape:` `portrait:` `motion-reduce:` `motion-safe:` `contrast-more:` `contrast-less:` `rtl:` `ltr:` `grid` `grid-cols-*` `ring-offset-*` `outline-*` `cursor-*` `bg-gradient-*` |
167
+
168
+ `ring`/`ring-{n}`/`ring-{color}` is a partial exception — RN has no box-shadow, so it falls back to `borderWidth`/`borderColor`, which *does* affect layout and shares properties with `border-*` (whichever class comes last wins if you combine both).
169
+
170
+ CSS inheritance doesn't exist in React Native — apply font utilities to each `Text`, or define a styled component once: `const Body = styled(Text, 'font-sans text-gray-10 dark:text-white');`.
171
+
172
+ ### Expo Web / React Native Web
173
+
174
+ In a browser (Expo Web, Metro web), `@kbach/react` switches to the same CSS-class strategy as plain web automatically:
175
+
176
+ - RN components substitute to HTML: `View`/`ScrollView`→`div`, `Text`→`span`, `TextInput`→`input`/`textarea`, `Image`→`img`, `Pressable`/`TouchableOpacity`→`div[role=button]`
177
+ - RN-only props (`onChangeText`, `source`, `secureTextEntry`, …) map to HTML equivalents
178
+ - Register more: `registerWebElement(Animated.View, 'div')`
179
+ - Recommended: use the Vite plugin same as [Static CSS setup](#static-css-setup) above — `import { kbach } from '@kbach/react/vite'` — and import `kbach.css` in your entry file, for zero runtime cost on the web target too
180
+ - Not using the Vite plugin (the common case for Expo/Metro web, which has no Vite build step)? Render `<KbachReset />` once near your root — e.g. Expo Router's root `app/_layout.tsx`, inside `<ThemeProvider>`:
181
+
182
+ ```jsx
183
+ import { KbachReset } from '@kbach/react';
184
+ import { ThemeProvider } from '@kbach/react/native';
185
+
186
+ export default function RootLayout() {
187
+ return (
188
+ <ThemeProvider defaultMode="system">
189
+ <KbachReset />
190
+ <Slot />
191
+ </ThemeProvider>
192
+ );
193
+ }
194
+ ```
195
+
196
+ This ships the base reset as real markup instead of relying solely on the runtime injector.
197
+
122
198
  ## Dark mode
123
199
 
124
200
  `<ThemeProvider>` powers every `dark:` class — detects OS color scheme, persists the user's choice, re-renders on change.
@@ -135,7 +211,7 @@ Don't also set up Static CSS above in the same app — pick one.
135
211
  | Prop | Type | Default | Description |
136
212
  |---|---|---|---|
137
213
  | `defaultMode` | `'light' \| 'dark' \| 'system'` | `'system'` | Starting mode |
138
- | `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` |
214
+ | `disablePersistence` | `boolean` | `false` | Skip saving to `localStorage` (web) / `AsyncStorage` (native) |
139
215
  | `config` | `FrameworkConfig` | global config | Scope a different config to this subtree |
140
216
 
141
217
  `darkMode` in `kbach.config.js` picks the matching strategy: `'attribute'` (default), `'class'`, or `'media'` (system-only). Toggle it with `useTheme()`'s `toggle()`/`setMode()` — see [API](#api).
@@ -364,6 +440,6 @@ Skipping the runtime one is an easy mistake under Static CSS setup specifically
364
440
 
365
441
  ## Full reference
366
442
 
367
- [kbach-react.md](./kbach-react.md) — complete utility list, every modifier, all config options.
443
+ [kbach-react.md](./kbach-react.md) — complete utility list, every modifier, all config options, covers web and React Native/Expo.
368
444
 
369
- React Native / Expo: [`@kbach/native`](https://www.npmjs.com/package/@kbach/native).
445
+ `@kbach/native` still exists on npm but is now just a deprecated compatibility shim re-exporting this package — install `@kbach/react` directly for new projects.
@@ -2868,7 +2868,16 @@ var BASE_RESET = [
2868
2868
  // plain text. Text-like inputs, textarea, and button don't have that problem
2869
2869
  // (their native chrome is just a skin around content utilities can fully
2870
2870
  // restyle), so they keep the blank-canvas treatment.
2871
- "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2871
+ // :where() wraps the :not() exclusions so they contribute ZERO specificity
2872
+ // (unlike a bare `input:not([type='checkbox']):not([type='radio'])`, whose
2873
+ // two :not([attr]) clauses each add a class-level specificity point — (0,2,1)
2874
+ // total, MORE than any single utility class (0,1,0)). Without :where(), this
2875
+ // reset's `color: inherit` always won the cascade over a text-* color
2876
+ // utility applied directly to a <input>/<textarea> regardless of source
2877
+ // order, since author rules only override on a tie or higher specificity —
2878
+ // confirmed on a real app: a TextInput's own text color utility resolved
2879
+ // and injected correctly, class and all, but silently never painted.
2880
+ "input:where(:not([type='checkbox']):not([type='radio'])), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2872
2881
  // Native checkbox/radio/range still get typography + spacing normalized, and
2873
2882
  // accent-color re-themes their native indicator to the current text color
2874
2883
  // instead of the browser/OS default blue, so they stay on-brand without
@@ -3837,14 +3846,14 @@ var _resizeModeMap = {
3837
3846
  "center": "none",
3838
3847
  "repeat": "none"
3839
3848
  };
3840
- var PLACEHOLDER_RULE_CLASS = "kbach-ph";
3849
+ var PLACEHOLDER_RULE_ATTR = "data-kbach-ph";
3841
3850
  var _placeholderRuleInjected = false;
3842
3851
  function ensurePlaceholderColorRuleInjected() {
3843
3852
  if (_placeholderRuleInjected || typeof document === "undefined") return;
3844
3853
  _placeholderRuleInjected = true;
3845
3854
  const style = document.createElement("style");
3846
3855
  style.setAttribute("data-kbach-placeholder", "");
3847
- style.textContent = `.${PLACEHOLDER_RULE_CLASS}::placeholder{color:var(--kbach-ph-color)}`;
3856
+ style.textContent = `[${PLACEHOLDER_RULE_ATTR}]::placeholder{color:var(--kbach-ph-color)}`;
3848
3857
  document.head.appendChild(style);
3849
3858
  }
3850
3859
  function transformToWebProps(originalName, tag, props) {
@@ -3854,7 +3863,6 @@ function transformToWebProps(originalName, tag, props) {
3854
3863
  const isImage = originalName === "Image" || originalName === "ImageBackground";
3855
3864
  const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
3856
3865
  let pendingStyle = null;
3857
- let pendingClassName = null;
3858
3866
  for (const [k, v] of Object.entries(props)) {
3859
3867
  if (_rnOnlyProps.has(k)) continue;
3860
3868
  if (k === "onPress") {
@@ -3889,7 +3897,7 @@ function transformToWebProps(originalName, tag, props) {
3889
3897
  if (k === "placeholderTextColor") {
3890
3898
  if (v) {
3891
3899
  ensurePlaceholderColorRuleInjected();
3892
- pendingClassName = PLACEHOLDER_RULE_CLASS;
3900
+ out[PLACEHOLDER_RULE_ATTR] = "";
3893
3901
  pendingStyle = { ...pendingStyle ?? {}, "--kbach-ph-color": v };
3894
3902
  }
3895
3903
  continue;
@@ -3955,9 +3963,6 @@ function transformToWebProps(originalName, tag, props) {
3955
3963
  if (pendingStyle) {
3956
3964
  out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
3957
3965
  }
3958
- if (pendingClassName) {
3959
- out.className = out.className ? `${out.className} ${pendingClassName}` : pendingClassName;
3960
- }
3961
3966
  if (isPressable && !out.role) out.role = "button";
3962
3967
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
3963
3968
  return out;
@@ -26,7 +26,7 @@ import {
26
26
  transformToWebProps,
27
27
  useConditionalGlobalDarkMode,
28
28
  useConditionalWidth
29
- } from "./chunk-CFUH3CUD.mjs";
29
+ } from "./chunk-KRRGLJLA.mjs";
30
30
 
31
31
  // src/jsx-runtime.tsx
32
32
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -233,7 +233,7 @@ declare function expandModeAwareColorClasses(classString: string, colors: ThemeC
233
233
  /**
234
234
  * Generates the KbachCustomColors/KbachCustomSpacing module-augmentation
235
235
  * source for every color/spacing key `extend.theme` added beyond the built-in
236
- * defaults. Called by both the Vite plugin and @kbach/native's Babel plugin —
236
+ * defaults. Called by both the Vite plugin and the Babel plugin (@kbach/react/babel-plugin) —
237
237
  * kept here, shared, so "which colors count as custom" and "flat string vs.
238
238
  * ColorScale" can't drift between the two.
239
239
  *
@@ -2956,7 +2956,16 @@ var BASE_RESET = [
2956
2956
  // plain text. Text-like inputs, textarea, and button don't have that problem
2957
2957
  // (their native chrome is just a skin around content utilities can fully
2958
2958
  // restyle), so they keep the blank-canvas treatment.
2959
- "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2959
+ // :where() wraps the :not() exclusions so they contribute ZERO specificity
2960
+ // (unlike a bare `input:not([type='checkbox']):not([type='radio'])`, whose
2961
+ // two :not([attr]) clauses each add a class-level specificity point — (0,2,1)
2962
+ // total, MORE than any single utility class (0,1,0)). Without :where(), this
2963
+ // reset's `color: inherit` always won the cascade over a text-* color
2964
+ // utility applied directly to a <input>/<textarea> regardless of source
2965
+ // order, since author rules only override on a tie or higher specificity —
2966
+ // confirmed on a real app: a TextInput's own text color utility resolved
2967
+ // and injected correctly, class and all, but silently never painted.
2968
+ "input:where(:not([type='checkbox']):not([type='radio'])), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2960
2969
  // Native checkbox/radio/range still get typography + spacing normalized, and
2961
2970
  // accent-color re-themes their native indicator to the current text color
2962
2971
  // instead of the browser/OS default blue, so they stay on-brand without
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { ReactNode, ComponentType, ForwardRefExoticComponent } from 'react';
2
+ import React__default, { Context, ReactNode, ComponentType, ForwardRefExoticComponent } from 'react';
3
3
  import * as __core from './core';
4
4
  import { ThemeMode, ResolvedConfig, DefaultColorName, defaultColors, ThemeColors, DefaultSpacingKey, ThemeSpacing, FrameworkConfig, StyleValue, ResolvedStyle } from './core';
5
5
  export { DefaultColorName, DefaultSpacingKey, FrameworkConfig, ParsedClass, PluginAPI, ResolvedConfig, ResolvedStyle, StyleValue, ThemeConfig, ThemeMode, buildConfig, clearCache, defaultColors, defaultTheme, disableRuntimeCSS, flatten, generateKbachTypesDts, getConfig, initConfig, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, updateConfig } from './core';
@@ -19,7 +19,10 @@ interface ThemeContextValue {
19
19
  /** The fully resolved framework config (theme values, darkMode strategy, etc.) */
20
20
  config: ResolvedConfig;
21
21
  }
22
- declare const ThemeContext: React.Context<ThemeContextValue | null>;
22
+ declare global {
23
+ var __kbachThemeContext: Context<ThemeContextValue | null> | undefined;
24
+ }
25
+ declare const ThemeContext: Context<ThemeContextValue | null>;
23
26
  declare function useTheme(): ThemeContextValue;
24
27
  declare function useIsDark(): boolean;
25
28
 
@@ -134,7 +137,7 @@ interface ThemeProviderProps {
134
137
  * System color scheme for native `defaultMode="system"`.
135
138
  *
136
139
  * Pass the value of `useColorScheme()` from `react-native`. When importing
137
- * `ThemeProvider` from `@kbach/native` this is handled automatically.
140
+ * `ThemeProvider` from `@kbach/react/native` this is handled automatically.
138
141
  *
139
142
  * @example
140
143
  * ```tsx
@@ -147,7 +150,7 @@ interface ThemeProviderProps {
147
150
  /**
148
151
  * Current window/screen width in pixels for responsive breakpoints.
149
152
  * On web this is read from `window.innerWidth` automatically.
150
- * When importing `ThemeProvider` from `@kbach/native` this is provided
153
+ * When importing `ThemeProvider` from `@kbach/react/native` this is provided
151
154
  * automatically from `useWindowDimensions()`.
152
155
  */
153
156
  windowWidth?: number;
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ module.exports = __toCommonJS(index_exports);
71
71
 
72
72
  // src/context.tsx
73
73
  var import_react = require("react");
74
- var ThemeContext = (0, import_react.createContext)(null);
74
+ var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = (0, import_react.createContext)(null));
75
75
  function useTheme() {
76
76
  const ctx = (0, import_react.useContext)(ThemeContext);
77
77
  if (!ctx) {
@@ -716,14 +716,14 @@ var _resizeModeMap = {
716
716
  "center": "none",
717
717
  "repeat": "none"
718
718
  };
719
- var PLACEHOLDER_RULE_CLASS = "kbach-ph";
719
+ var PLACEHOLDER_RULE_ATTR = "data-kbach-ph";
720
720
  var _placeholderRuleInjected = false;
721
721
  function ensurePlaceholderColorRuleInjected() {
722
722
  if (_placeholderRuleInjected || typeof document === "undefined") return;
723
723
  _placeholderRuleInjected = true;
724
724
  const style = document.createElement("style");
725
725
  style.setAttribute("data-kbach-placeholder", "");
726
- style.textContent = `.${PLACEHOLDER_RULE_CLASS}::placeholder{color:var(--kbach-ph-color)}`;
726
+ style.textContent = `[${PLACEHOLDER_RULE_ATTR}]::placeholder{color:var(--kbach-ph-color)}`;
727
727
  document.head.appendChild(style);
728
728
  }
729
729
  function transformToWebProps(originalName, tag, props) {
@@ -733,7 +733,6 @@ function transformToWebProps(originalName, tag, props) {
733
733
  const isImage = originalName === "Image" || originalName === "ImageBackground";
734
734
  const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
735
735
  let pendingStyle = null;
736
- let pendingClassName = null;
737
736
  for (const [k, v] of Object.entries(props)) {
738
737
  if (_rnOnlyProps.has(k)) continue;
739
738
  if (k === "onPress") {
@@ -768,7 +767,7 @@ function transformToWebProps(originalName, tag, props) {
768
767
  if (k === "placeholderTextColor") {
769
768
  if (v) {
770
769
  ensurePlaceholderColorRuleInjected();
771
- pendingClassName = PLACEHOLDER_RULE_CLASS;
770
+ out[PLACEHOLDER_RULE_ATTR] = "";
772
771
  pendingStyle = { ...pendingStyle ?? {}, "--kbach-ph-color": v };
773
772
  }
774
773
  continue;
@@ -834,9 +833,6 @@ function transformToWebProps(originalName, tag, props) {
834
833
  if (pendingStyle) {
835
834
  out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
836
835
  }
837
- if (pendingClassName) {
838
- out.className = out.className ? `${out.className} ${pendingClassName}` : pendingClassName;
839
- }
840
836
  if (isPressable && !out.role) out.role = "button";
841
837
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
842
838
  return out;
package/dist/index.mjs CHANGED
@@ -48,11 +48,11 @@ import {
48
48
  useGlobalDarkMode,
49
49
  useGlobalWidth,
50
50
  useSyncExternalStore
51
- } from "./chunk-CFUH3CUD.mjs";
51
+ } from "./chunk-KRRGLJLA.mjs";
52
52
 
53
53
  // src/context.tsx
54
54
  import { createContext, useContext } from "react";
55
- var ThemeContext = createContext(null);
55
+ var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = createContext(null));
56
56
  function useTheme() {
57
57
  const ctx = useContext(ThemeContext);
58
58
  if (!ctx) {
@@ -500,14 +500,14 @@ var _resizeModeMap = {
500
500
  "center": "none",
501
501
  "repeat": "none"
502
502
  };
503
- var PLACEHOLDER_RULE_CLASS = "kbach-ph";
503
+ var PLACEHOLDER_RULE_ATTR = "data-kbach-ph";
504
504
  var _placeholderRuleInjected = false;
505
505
  function ensurePlaceholderColorRuleInjected() {
506
506
  if (_placeholderRuleInjected || typeof document === "undefined") return;
507
507
  _placeholderRuleInjected = true;
508
508
  const style = document.createElement("style");
509
509
  style.setAttribute("data-kbach-placeholder", "");
510
- style.textContent = `.${PLACEHOLDER_RULE_CLASS}::placeholder{color:var(--kbach-ph-color)}`;
510
+ style.textContent = `[${PLACEHOLDER_RULE_ATTR}]::placeholder{color:var(--kbach-ph-color)}`;
511
511
  document.head.appendChild(style);
512
512
  }
513
513
  function transformToWebProps(originalName, tag, props) {
@@ -517,7 +517,6 @@ function transformToWebProps(originalName, tag, props) {
517
517
  const isImage = originalName === "Image" || originalName === "ImageBackground";
518
518
  const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
519
519
  let pendingStyle = null;
520
- let pendingClassName = null;
521
520
  for (const [k, v] of Object.entries(props)) {
522
521
  if (_rnOnlyProps.has(k)) continue;
523
522
  if (k === "onPress") {
@@ -552,7 +551,7 @@ function transformToWebProps(originalName, tag, props) {
552
551
  if (k === "placeholderTextColor") {
553
552
  if (v) {
554
553
  ensurePlaceholderColorRuleInjected();
555
- pendingClassName = PLACEHOLDER_RULE_CLASS;
554
+ out[PLACEHOLDER_RULE_ATTR] = "";
556
555
  pendingStyle = { ...pendingStyle ?? {}, "--kbach-ph-color": v };
557
556
  }
558
557
  continue;
@@ -618,9 +617,6 @@ function transformToWebProps(originalName, tag, props) {
618
617
  if (pendingStyle) {
619
618
  out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
620
619
  }
621
- if (pendingClassName) {
622
- out.className = out.className ? `${out.className} ${pendingClassName}` : pendingClassName;
623
- }
624
620
  if (isPressable && !out.role) out.role = "button";
625
621
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
626
622
  return out;
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-YI75WFHE.mjs";
7
- import "./chunk-CFUH3CUD.mjs";
6
+ } from "./chunk-LIUJPOTS.mjs";
7
+ import "./chunk-KRRGLJLA.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -506,14 +506,14 @@ var _resizeModeMap = {
506
506
  "center": "none",
507
507
  "repeat": "none"
508
508
  };
509
- var PLACEHOLDER_RULE_CLASS = "kbach-ph";
509
+ var PLACEHOLDER_RULE_ATTR = "data-kbach-ph";
510
510
  var _placeholderRuleInjected = false;
511
511
  function ensurePlaceholderColorRuleInjected() {
512
512
  if (_placeholderRuleInjected || typeof document === "undefined") return;
513
513
  _placeholderRuleInjected = true;
514
514
  const style = document.createElement("style");
515
515
  style.setAttribute("data-kbach-placeholder", "");
516
- style.textContent = `.${PLACEHOLDER_RULE_CLASS}::placeholder{color:var(--kbach-ph-color)}`;
516
+ style.textContent = `[${PLACEHOLDER_RULE_ATTR}]::placeholder{color:var(--kbach-ph-color)}`;
517
517
  document.head.appendChild(style);
518
518
  }
519
519
  function transformToWebProps(originalName, tag, props) {
@@ -523,7 +523,6 @@ function transformToWebProps(originalName, tag, props) {
523
523
  const isImage = originalName === "Image" || originalName === "ImageBackground";
524
524
  const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
525
525
  let pendingStyle = null;
526
- let pendingClassName = null;
527
526
  for (const [k, v] of Object.entries(props)) {
528
527
  if (_rnOnlyProps.has(k)) continue;
529
528
  if (k === "onPress") {
@@ -558,7 +557,7 @@ function transformToWebProps(originalName, tag, props) {
558
557
  if (k === "placeholderTextColor") {
559
558
  if (v) {
560
559
  ensurePlaceholderColorRuleInjected();
561
- pendingClassName = PLACEHOLDER_RULE_CLASS;
560
+ out[PLACEHOLDER_RULE_ATTR] = "";
562
561
  pendingStyle = { ...pendingStyle ?? {}, "--kbach-ph-color": v };
563
562
  }
564
563
  continue;
@@ -624,9 +623,6 @@ function transformToWebProps(originalName, tag, props) {
624
623
  if (pendingStyle) {
625
624
  out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
626
625
  }
627
- if (pendingClassName) {
628
- out.className = out.className ? `${out.className} ${pendingClassName}` : pendingClassName;
629
- }
630
626
  if (isPressable && !out.role) out.role = "button";
631
627
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
632
628
  return out;
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-YI75WFHE.mjs";
7
+ } from "./chunk-LIUJPOTS.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-CFUH3CUD.mjs";
10
+ } from "./chunk-KRRGLJLA.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,