@kbach/react 0.6.9 → 0.6.11

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
@@ -20,7 +20,7 @@ npm install @kbach/react
20
20
 
21
21
  ## Setup
22
22
 
23
- Two steps, and you're done — styles work immediately via runtime CSS injection, no build plugin required:
23
+ Step 1 is always required. After that, pick **one** of the two setups below — Quick if you just want styles working, Static CSS if you're shipping to production and/or using SSR.
24
24
 
25
25
  ### 1. Configure the JSX runtime
26
26
 
@@ -39,25 +39,30 @@ Don't also pass `jsxImportSource` to `@vitejs/plugin-react` (or any other bundle
39
39
 
40
40
  Alternatives if you can't use tsconfig: a per-file `/** @jsxImportSource @kbach/react */` pragma comment, or `['@babel/preset-react', { runtime: 'automatic', importSource: '@kbach/react' }]` in `babel.config.js` for non-Vite/non-SWC toolchains.
41
41
 
42
- ### 2. Wrap your app
42
+ ### 2a. Quick setup (runtime CSS injection)
43
+
44
+ No build plugin, works with any bundler (Vite, webpack, Turbopack, Metro-for-web, …). Styles are generated and injected into a `<style>` tag by client-side JS the moment `ThemeProvider` mounts.
43
45
 
44
46
  ```jsx
45
- import { ThemeProvider } from '@kbach/react';
47
+ import { ThemeProvider, KbachReset } from '@kbach/react';
46
48
 
47
49
  export default function Root() {
48
50
  return (
49
51
  <ThemeProvider defaultMode="system">
52
+ <KbachReset />
50
53
  <App />
51
54
  </ThemeProvider>
52
55
  );
53
56
  }
54
57
  ```
55
58
 
56
- That's the whole setup. `@kbach/react` ships its own `"use client"` directive, so this works directly in a Next.js App Router Server Component tree too — no manual client wrapper needed.
59
+ `<KbachReset />` renders Kbach's base browser-default reset (borderless buttons/inputs, visible checkboxes/radios, no arrow-less `<select>`, etc. — see [CSS resets](#css-resets) below) as a plain `<style>` tag instead of waiting for the client-side injector. On a plain client-rendered app (Vite CSR, Create React App, …) it's a nice-to-have, since the injector already runs before first paint there. **On SSR** (Next.js, React Router framework mode, Remix, …) it's the difference between correct styling from the first byte and a flash of raw browser defaults (default button border, unstyled `<select>`, serif-ish fonts, …) that only clears up once hydration finishes — the server has no JS to run the injector, but it *can* render this component, since it's just JSX. Put it as high in `<head>` as your framework allows; the snippet above (right after `ThemeProvider` opens) works everywhere `<head>` isn't directly reachable.
60
+
61
+ That's the whole setup. `@kbach/react` ships its own `"use client"` directive, so both of these work directly in a Next.js App Router Server Component tree — no manual client wrapper needed.
57
62
 
58
- ### Optional: static CSS for production (Vite only)
63
+ ### 2b. Static CSS setup (recommended for production / SSR, Vite only)
59
64
 
60
- By default, styles are generated and injected into a `<style>` tag by client-side JS at runtime — simple, but it means server-rendered HTML has no matching CSS until hydration runs (a brief flash of unstyled content on first paint). If you're on a Vite-based project and want zero-runtime-cost CSS instead, add the `@kbach/react/vite` plugin:
65
+ Zero runtime cost: a Vite plugin scans your source at build time and writes real CSS into a file you import, so styles ship as an ordinary stylesheet with no client-side generation step at all — nothing to flash-of-unstyled before. This is the better choice for any Vite-based SSR framework (React Router, SvelteKit-style meta-frameworks, etc.) over the Quick setup + `<KbachReset />` combination, since it covers *every* class, not just the base reset.
61
66
 
62
67
  ```ts
63
68
  // vite.config.ts
@@ -88,15 +93,26 @@ Then create an empty `kbach.css` anywhere in your project and import it once in
88
93
  import './kbach.css';
89
94
  ```
90
95
 
96
+ ```jsx
97
+ import { ThemeProvider } from '@kbach/react';
98
+ // No <KbachReset /> needed — kbach.css already includes the same base reset.
99
+
100
+ export default function Root() {
101
+ return (
102
+ <ThemeProvider defaultMode="system">
103
+ <App />
104
+ </ThemeProvider>
105
+ );
106
+ }
107
+ ```
108
+
91
109
  The plugin scans your source files and writes generated CSS between the `/* kbach:start */`/`/* kbach:end */` markers. Importing `kbach.css` automatically disables runtime injection — all styles come from the file instead. On HMR, only the changed file is rescanned; unchanged tokens reuse cached CSS.
92
110
 
93
111
  While it's scanning, the plugin also indexes every `.css`/`.scss`/`.sass`/`.less` file under the same directories and warns in the terminal (not the browser console) for any class that's neither a real Kbach utility nor defined anywhere in those stylesheets — likely a typo. A class you've defined yourself elsewhere (CSS Modules, styled-components, a third-party component's class) is recognized as soon as something in the project literally has a `.that-class-name` rule, so it won't get flagged just because Kbach itself doesn't know it. Each warning includes a `file:line:column` location that most terminals (VS Code's integrated terminal included) turn into a clickable link straight to that class.
94
112
 
95
113
  ## Next.js
96
114
 
97
- Step 1 (tsconfig `jsxImportSource`) and step 2 (`ThemeProvider`) above apply as-is — Next.js's SWC compiler reads `jsxImportSource` from `tsconfig.json` the same way Vite does, and `@kbach/react` shipping its own `"use client"` directive means `ThemeProvider` works directly in a Server Component tree (e.g. the App Router root `layout.tsx`) with no wrapper of your own needed.
98
-
99
- The "Optional: static CSS" step above doesn't apply — Next.js builds with webpack/Turbopack, not Vite, so there's currently no static-CSS build step for it. Styles fall back to runtime injection, which means the same first-paint FOUC note above applies: expect a brief flash of unstyled content before hydration completes. This is a known limitation, not a per-project bug to work around.
115
+ Step 1 (tsconfig `jsxImportSource`) above applies as-is — Next.js's SWC compiler reads `jsxImportSource` from `tsconfig.json` the same way Vite does. For step 2, use **Quick setup (2a)** — Next.js builds with webpack/Turbopack, not Vite, so the static-CSS plugin (2b) doesn't apply. Render `<KbachReset />` once in the root App Router `layout.tsx` (inside `<head>`, or right after `<ThemeProvider>` opens) so Server Component HTML gets the base reset without waiting on hydration; `@kbach/react` shipping its own `"use client"` directive means both `ThemeProvider` and `KbachReset` work directly in that Server Component tree with no wrapper of your own needed.
100
116
 
101
117
  ## React Router
102
118
 
@@ -117,6 +133,8 @@ export default defineConfig({
117
133
 
118
134
  If you do add `kbach()` to `vite.config.ts` and it detects both plugins active together, it prints a warning explaining exactly this at dev-server startup — so this doesn't have to be a silent, hard-to-diagnose blank page.
119
135
 
136
+ Framework mode is SSR, so skipping the static-CSS plugin (2b) in favor of Quick setup (2a) means the initial server-rendered HTML has no CSS at all until hydration — render `<KbachReset />` in `root.tsx`'s `<Layout>` (inside `<head>`, alongside `<Links />`/`<Meta />`) if you go that route, otherwise expect raw browser defaults (e.g. the native `<button>` border) on first paint.
137
+
120
138
  **Library mode (client-only, `createBrowserRouter`/`<BrowserRouter>`):** no SSR involved — set it up exactly like any other Vite + React app. Since there's no meta-framework Vite plugin providing JSX handling here, you likely do need `@vitejs/plugin-react({ jsxImportSource: '@kbach/react' })` (or rely on tsconfig alone — Vite's default esbuild-based transform reads it too).
121
139
 
122
140
  ## API
@@ -355,7 +373,7 @@ Opacity modifier: `bg-blue-6/50` (50% alpha), `bg-blue-6/[0.15]` (arbitrary)
355
373
 
356
374
  ## CSS resets
357
375
 
358
- Included automatically in `kbach.css` and in runtime injection:
376
+ Included automatically in `kbach.css`, in runtime injection, and in `<KbachReset />` (all three render the exact same rules — pick whichever setup you're using from [Setup](#setup) above):
359
377
 
360
378
  - `*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor }` — border utilities like `border-2 border-gray-4` work on any element without also needing `border-solid`
361
379
  - `body { margin: 0; padding: 0 }`
@@ -363,8 +381,9 @@ Included automatically in `kbach.css` and in runtime injection:
363
381
  - `p`, `ul`, `ol` — margin and list style cleared
364
382
  - `a` — color and underline cleared (inherit from parent)
365
383
  - `img`, `video`, `svg` — `display: block; max-width: 100%`
366
- - `input`, `textarea`, `select`, `button`, `fieldset` — appearance, border, padding cleared
367
- - `table` — `border-collapse: collapse`
384
+ - `button`, text-like `input`s (not checkbox/radio), `textarea` — appearance, border, padding cleared, so `border-`/`bg-`/`rounded-`/`p-` utilities fully restyle them
385
+ - `input[type=checkbox]`, `input[type=radio]`, `select` — native rendering kept (stripping it would hide the checkmark/dot/dropdown-arrow with nothing to replace them); only typography and spacing are normalized, and checkbox/radio/range get `accent-color: currentColor` so they still pick up your text color instead of the OS default blue
386
+ - `fieldset`, `table` — padding/margin/border-collapse cleared
368
387
 
369
388
  ## Configuration
370
389
 
@@ -2549,6 +2549,7 @@ function findOuterColon(s) {
2549
2549
  }
2550
2550
 
2551
2551
  // src/core/reset.ts
2552
+ var RESET_STYLE_ID = "kbach-reset";
2552
2553
  var BASE_RESET = [
2553
2554
  // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
2554
2555
  // border-width: 0 keeps all elements borderless by default.
@@ -2752,7 +2753,7 @@ function getGlobalStyleEl() {
2752
2753
  }
2753
2754
  function injectGlobalStyles(theme) {
2754
2755
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2755
- const rules = [BASE_RESET];
2756
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2756
2757
  const ff = theme.fontFamily;
2757
2758
  if (ff?.sans && ff.sans !== "System") {
2758
2759
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3693,19 +3694,24 @@ var InteractiveWrapper = forwardRef(
3693
3694
  ...checked !== void 0 ? { checked } : {},
3694
3695
  style: finalStyle,
3695
3696
  ...!isNative && className ? { className } : {},
3696
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
3697
- // and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
3698
- // component (react-native-web components accept both prop names), never when it's a
3699
- // literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
3700
- // property" for onPressIn/onPressOut on a host element even when the value is
3701
- // undefined — the check is on the prop KEY being present at all, not its value — so
3702
- // the key must be omitted entirely for string components, not just set to undefined.
3697
+ // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
3698
+ // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
3699
+ // Component isn't a literal HTML tag string. A non-string Component on web is just as
3700
+ // likely to be an ordinary web component (React Router's <Link>, Next.js's <Link>, any
3701
+ // custom wrapper) as an actual react-native-web primitive — "not a string" alone was never
3702
+ // a reliable signal that onPressIn/onPressOut are wanted, and forwarding them
3703
+ // unconditionally made React DOM warn "Unknown event handler property" the moment any web
3704
+ // component got an interactive modifier (hover:, active:, …), which is the common case,
3705
+ // not the exception. (A real react-native-web primitive would additionally have this pair
3706
+ // of props silently vanish after hydration anyway, once the browser-only web-substitution
3707
+ // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
3708
+ // only have traded one prop-mismatch warning for another.) Genuinely native code paths
3709
+ // still get them via the isNative branch below.
3703
3710
  ...!isNative ? {
3704
3711
  onPointerDown: handlePointerDown,
3705
3712
  onPointerUp: handlePointerUp,
3706
3713
  onPointerLeave: handlePointerLeave,
3707
- onPointerCancel: handlePointerCancel,
3708
- ...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
3714
+ onPointerCancel: handlePointerCancel
3709
3715
  } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3710
3716
  onMouseEnter: handleMouseEnter,
3711
3717
  onMouseLeave: handleMouseLeave,
@@ -3720,6 +3726,8 @@ InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
3720
3726
  export {
3721
3727
  defaultColors,
3722
3728
  defaultTheme,
3729
+ RESET_STYLE_ID,
3730
+ BASE_RESET,
3723
3731
  isWeb,
3724
3732
  isNative,
3725
3733
  setResolveTarget,
@@ -23,7 +23,7 @@ import {
23
23
  transformToWebProps,
24
24
  useConditionalWidth,
25
25
  useGlobalDarkMode
26
- } from "./chunk-XJFOY7HN.mjs";
26
+ } from "./chunk-OWJUMH2V.mjs";
27
27
 
28
28
  // src/jsx-runtime.tsx
29
29
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
package/dist/index.d.mts CHANGED
@@ -271,6 +271,24 @@ interface ThemeProviderProps {
271
271
  }
272
272
  declare function ThemeProvider({ children, defaultMode, colorScheme, windowWidth: windowWidthProp, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
273
273
 
274
+ /**
275
+ * Renders Kbach's base browser-default reset (borderless button/input,
276
+ * visible checkbox/radio, no arrow-less <select>, etc.) as a plain <style>
277
+ * tag so it's part of the page's initial HTML.
278
+ *
279
+ * Runtime-only setups (no Vite plugin / static kbach.css) otherwise only get
280
+ * this reset once ThemeProvider's client-side effect runs — fine for a plain
281
+ * CSR app, but under SSR the server has no JS to run it, so the first paint
282
+ * ships with raw browser defaults (e.g. the native button border) until
283
+ * hydration catches up. Render this once, as high in <head> as your
284
+ * framework allows, to close that gap.
285
+ *
286
+ * Not needed if you're on the static-CSS setup — kbach.css already includes
287
+ * the same reset, and the runtime injector detects this tag and skips adding
288
+ * it a second time either way.
289
+ */
290
+ declare function KbachReset(): React.JSX.Element | null;
291
+
274
292
  interface StyledProps {
275
293
  /** Additional utility classes applied on top of the base classes */
276
294
  kb?: string;
@@ -417,4 +435,4 @@ declare function kb(classString: string, isDark?: boolean): StyleValue | string;
417
435
  */
418
436
  declare function cx(...classes: Array<string | false | null | undefined>): string;
419
437
 
420
- export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, buildConfig, clearCache, cx, defaultColors, defaultTheme, disableRuntimeCSS, flatten, getConfig, initConfig, kb, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, styled, updateConfig, useBreakpoint, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useResponsive, useStyles, useTheme, wrapColors };
438
+ export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, KbachReset, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, buildConfig, clearCache, cx, defaultColors, defaultTheme, disableRuntimeCSS, flatten, getConfig, initConfig, kb, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, styled, updateConfig, useBreakpoint, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useResponsive, useStyles, useTheme, wrapColors };
package/dist/index.d.ts CHANGED
@@ -271,6 +271,24 @@ interface ThemeProviderProps {
271
271
  }
272
272
  declare function ThemeProvider({ children, defaultMode, colorScheme, windowWidth: windowWidthProp, config: configOverride, disablePersistence, }: ThemeProviderProps): React__default.JSX.Element;
273
273
 
274
+ /**
275
+ * Renders Kbach's base browser-default reset (borderless button/input,
276
+ * visible checkbox/radio, no arrow-less <select>, etc.) as a plain <style>
277
+ * tag so it's part of the page's initial HTML.
278
+ *
279
+ * Runtime-only setups (no Vite plugin / static kbach.css) otherwise only get
280
+ * this reset once ThemeProvider's client-side effect runs — fine for a plain
281
+ * CSR app, but under SSR the server has no JS to run it, so the first paint
282
+ * ships with raw browser defaults (e.g. the native button border) until
283
+ * hydration catches up. Render this once, as high in <head> as your
284
+ * framework allows, to close that gap.
285
+ *
286
+ * Not needed if you're on the static-CSS setup — kbach.css already includes
287
+ * the same reset, and the runtime injector detects this tag and skips adding
288
+ * it a second time either way.
289
+ */
290
+ declare function KbachReset(): React.JSX.Element | null;
291
+
274
292
  interface StyledProps {
275
293
  /** Additional utility classes applied on top of the base classes */
276
294
  kb?: string;
@@ -417,4 +435,4 @@ declare function kb(classString: string, isDark?: boolean): StyleValue | string;
417
435
  */
418
436
  declare function cx(...classes: Array<string | false | null | undefined>): string;
419
437
 
420
- export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, buildConfig, clearCache, cx, defaultColors, defaultTheme, disableRuntimeCSS, flatten, getConfig, initConfig, kb, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, styled, updateConfig, useBreakpoint, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useResponsive, useStyles, useTheme, wrapColors };
438
+ export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, KbachReset, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, buildConfig, clearCache, cx, defaultColors, defaultTheme, disableRuntimeCSS, flatten, getConfig, initConfig, kb, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, styled, updateConfig, useBreakpoint, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useResponsive, useStyles, useTheme, wrapColors };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
34
  InteractiveWrapper: () => InteractiveWrapper,
35
+ KbachReset: () => KbachReset,
35
36
  ThemeContext: () => ThemeContext,
36
37
  ThemeProvider: () => ThemeProvider,
37
38
  buildConfig: () => buildConfig,
@@ -627,6 +628,47 @@ var defaultTheme = {
627
628
  }
628
629
  };
629
630
 
631
+ // src/core/reset.ts
632
+ var RESET_STYLE_ID = "kbach-reset";
633
+ var BASE_RESET = [
634
+ // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
635
+ // border-width: 0 keeps all elements borderless by default.
636
+ "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
637
+ "body { margin: 0; padding: 0; }",
638
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
639
+ "p { margin: 0; }",
640
+ "a { color: inherit; text-decoration: none; }",
641
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
642
+ "img, video, svg { display: block; max-width: 100%; }",
643
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
644
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
645
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
646
+ // plain text. Text-like inputs, textarea, and button don't have that problem
647
+ // (their native chrome is just a skin around content utilities can fully
648
+ // restyle), so they keep the blank-canvas treatment.
649
+ "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; }",
650
+ // Native checkbox/radio/range still get typography + spacing normalized, and
651
+ // accent-color re-themes their native indicator to the current text color
652
+ // instead of the browser/OS default blue, so they stay on-brand without
653
+ // needing to be rebuilt from scratch.
654
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
655
+ // select keeps its native chrome (border/background/arrow are all part of the
656
+ // same OS-drawn widget that appearance: none would blank out) — only typography
657
+ // and spacing are normalized so it still matches surrounding text.
658
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
659
+ "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
660
+ "button, [role='button'] { cursor: pointer; }",
661
+ ":disabled { cursor: default; }",
662
+ "textarea { resize: vertical; }",
663
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
664
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
665
+ "::placeholder { opacity: 1; }",
666
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
667
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
668
+ "fieldset { padding: 0; margin: 0; }",
669
+ "table { border-collapse: collapse; border-spacing: 0; }"
670
+ ].join("\n");
671
+
630
672
  // src/core/cache.ts
631
673
  var LRUCache = class {
632
674
  constructor(capacity = 1e4, onEvict) {
@@ -2667,46 +2709,6 @@ function findOuterColon(s) {
2667
2709
  return -1;
2668
2710
  }
2669
2711
 
2670
- // src/core/reset.ts
2671
- var BASE_RESET = [
2672
- // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
2673
- // border-width: 0 keeps all elements borderless by default.
2674
- "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
2675
- "body { margin: 0; padding: 0; }",
2676
- "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2677
- "p { margin: 0; }",
2678
- "a { color: inherit; text-decoration: none; }",
2679
- "ul, ol { margin: 0; padding: 0; list-style: none; }",
2680
- "img, video, svg { display: block; max-width: 100%; }",
2681
- // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2682
- // stripping it hides their native checkmark/arrow with nothing rendered in its
2683
- // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2684
- // plain text. Text-like inputs, textarea, and button don't have that problem
2685
- // (their native chrome is just a skin around content utilities can fully
2686
- // restyle), so they keep the blank-canvas treatment.
2687
- "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; }",
2688
- // Native checkbox/radio/range still get typography + spacing normalized, and
2689
- // accent-color re-themes their native indicator to the current text color
2690
- // instead of the browser/OS default blue, so they stay on-brand without
2691
- // needing to be rebuilt from scratch.
2692
- "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2693
- // select keeps its native chrome (border/background/arrow are all part of the
2694
- // same OS-drawn widget that appearance: none would blank out) — only typography
2695
- // and spacing are normalized so it still matches surrounding text.
2696
- "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2697
- "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2698
- "button, [role='button'] { cursor: pointer; }",
2699
- ":disabled { cursor: default; }",
2700
- "textarea { resize: vertical; }",
2701
- // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2702
- // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2703
- "::placeholder { opacity: 1; }",
2704
- "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2705
- "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2706
- "fieldset { padding: 0; margin: 0; }",
2707
- "table { border-collapse: collapse; border-spacing: 0; }"
2708
- ].join("\n");
2709
-
2710
2712
  // src/core/responsiveStore.ts
2711
2713
  var RESPONSIVE_KEY = "__kbach_responsive__";
2712
2714
  function getStore() {
@@ -2830,7 +2832,7 @@ function getGlobalStyleEl() {
2830
2832
  }
2831
2833
  function injectGlobalStyles(theme) {
2832
2834
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2833
- const rules = [BASE_RESET];
2835
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2834
2836
  const ff = theme.fontFamily;
2835
2837
  if (ff?.sans && ff.sans !== "System") {
2836
2838
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3524,6 +3526,13 @@ function ThemeProvider({
3524
3526
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: contextValue, children });
3525
3527
  }
3526
3528
 
3529
+ // src/KbachReset.tsx
3530
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3531
+ function KbachReset() {
3532
+ if (isNative) return null;
3533
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { id: RESET_STYLE_ID, children: BASE_RESET });
3534
+ }
3535
+
3527
3536
  // src/styled.tsx
3528
3537
  var import_react5 = __toESM(require("react"));
3529
3538
 
@@ -3979,25 +3988,28 @@ function styled(Component, baseClasses = "") {
3979
3988
  ...isWeb && combined ? { className: normalizeClassString(combined) } : {},
3980
3989
  // Only attach state-tracking handlers when interactive modifiers are present.
3981
3990
  // Always forward user-provided handlers to avoid silently swallowing them.
3982
- // onPressIn/onPressOut are forwarded on web too (not just native) since
3983
- // react-native-web components rely on receiving them directly — but only
3984
- // when effectiveComponent is an actual component, never a literal HTML tag
3985
- // string ('div', 'a', …). React DOM warns "Unknown event handler property"
3986
- // for onPressIn/onPressOut on a host element even when the value is
3987
- // undefined — the check is on the prop KEY being present at all, not its
3988
- // value — so the key must be omitted entirely for string components.
3989
- ...isWeb ? hasInteractive ? {
3991
+ // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
3992
+ // and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
3993
+ // getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
3994
+ // a non-string RN-style reference server-side, and this used to forward onPressIn/
3995
+ // onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
3996
+ // covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
3997
+ // non-string effectiveComponent on the web/SSR side is just as likely to be an
3998
+ // ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
3999
+ // wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
4000
+ // reliable signal either way. Forwarding them made React DOM warn "Unknown event
4001
+ // handler property" the moment any such component got an interactive modifier
4002
+ // (hover:, active:, …), which is the common case, not the exception.
4003
+ ...!isNative ? hasInteractive ? {
3990
4004
  onPointerDown: handlePointerDown,
3991
4005
  onPointerUp: handlePointerUp,
3992
4006
  onPointerLeave: handlePointerLeave,
3993
- onPointerCancel: handlePointerCancel,
3994
- ...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
4007
+ onPointerCancel: handlePointerCancel
3995
4008
  } : {
3996
4009
  onPointerDown,
3997
4010
  onPointerUp,
3998
4011
  onPointerLeave,
3999
- onPointerCancel,
4000
- ...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
4012
+ onPointerCancel
4001
4013
  } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
4002
4014
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
4003
4015
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
@@ -4118,19 +4130,24 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
4118
4130
  ...checked !== void 0 ? { checked } : {},
4119
4131
  style: finalStyle,
4120
4132
  ...!isNative && className ? { className } : {},
4121
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
4122
- // and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
4123
- // component (react-native-web components accept both prop names), never when it's a
4124
- // literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
4125
- // property" for onPressIn/onPressOut on a host element even when the value is
4126
- // undefined — the check is on the prop KEY being present at all, not its value — so
4127
- // the key must be omitted entirely for string components, not just set to undefined.
4133
+ // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
4134
+ // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
4135
+ // Component isn't a literal HTML tag string. A non-string Component on web is just as
4136
+ // likely to be an ordinary web component (React Router's <Link>, Next.js's <Link>, any
4137
+ // custom wrapper) as an actual react-native-web primitive — "not a string" alone was never
4138
+ // a reliable signal that onPressIn/onPressOut are wanted, and forwarding them
4139
+ // unconditionally made React DOM warn "Unknown event handler property" the moment any web
4140
+ // component got an interactive modifier (hover:, active:, …), which is the common case,
4141
+ // not the exception. (A real react-native-web primitive would additionally have this pair
4142
+ // of props silently vanish after hydration anyway, once the browser-only web-substitution
4143
+ // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
4144
+ // only have traded one prop-mismatch warning for another.) Genuinely native code paths
4145
+ // still get them via the isNative branch below.
4128
4146
  ...!isNative ? {
4129
4147
  onPointerDown: handlePointerDown,
4130
4148
  onPointerUp: handlePointerUp,
4131
4149
  onPointerLeave: handlePointerLeave,
4132
- onPointerCancel: handlePointerCancel,
4133
- ...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
4150
+ onPointerCancel: handlePointerCancel
4134
4151
  } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
4135
4152
  onMouseEnter: handleMouseEnter,
4136
4153
  onMouseLeave: handleMouseLeave,
@@ -4157,6 +4174,7 @@ function cx(...classes) {
4157
4174
  // Annotate the CommonJS export names for ESM import in node:
4158
4175
  0 && (module.exports = {
4159
4176
  InteractiveWrapper,
4177
+ KbachReset,
4160
4178
  ThemeContext,
4161
4179
  ThemeProvider,
4162
4180
  buildConfig,
package/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  'use client';
2
2
  import {
3
+ BASE_RESET,
3
4
  EMPTY_BREAKPOINTS,
4
5
  InteractiveWrapper,
6
+ RESET_STYLE_ID,
5
7
  buildConfig,
6
8
  chain,
7
9
  clearCache,
@@ -15,6 +17,7 @@ import {
15
17
  hasInteractiveBuckets,
16
18
  hasResponsiveBuckets,
17
19
  initConfig,
20
+ isNative,
18
21
  isWeb,
19
22
  normalizeClassString,
20
23
  onConfigChange,
@@ -36,7 +39,7 @@ import {
36
39
  useGlobalDarkMode,
37
40
  useGlobalWidth,
38
41
  useSyncExternalStore
39
- } from "./chunk-XJFOY7HN.mjs";
42
+ } from "./chunk-OWJUMH2V.mjs";
40
43
 
41
44
  // src/context.tsx
42
45
  import { createContext, useContext } from "react";
@@ -262,6 +265,13 @@ function ThemeProvider({
262
265
  return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
263
266
  }
264
267
 
268
+ // src/KbachReset.tsx
269
+ import { jsx as jsx2 } from "react/jsx-runtime";
270
+ function KbachReset() {
271
+ if (isNative) return null;
272
+ return /* @__PURE__ */ jsx2("style", { id: RESET_STYLE_ID, children: BASE_RESET });
273
+ }
274
+
265
275
  // src/styled.tsx
266
276
  import React2, {
267
277
  forwardRef,
@@ -329,25 +339,28 @@ function styled(Component, baseClasses = "") {
329
339
  ...isWeb && combined ? { className: normalizeClassString(combined) } : {},
330
340
  // Only attach state-tracking handlers when interactive modifiers are present.
331
341
  // Always forward user-provided handlers to avoid silently swallowing them.
332
- // onPressIn/onPressOut are forwarded on web too (not just native) since
333
- // react-native-web components rely on receiving them directly — but only
334
- // when effectiveComponent is an actual component, never a literal HTML tag
335
- // string ('div', 'a', …). React DOM warns "Unknown event handler property"
336
- // for onPressIn/onPressOut on a host element even when the value is
337
- // undefined — the check is on the prop KEY being present at all, not its
338
- // value — so the key must be omitted entirely for string components.
339
- ...isWeb ? hasInteractive ? {
342
+ // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
343
+ // and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
344
+ // getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
345
+ // a non-string RN-style reference server-side, and this used to forward onPressIn/
346
+ // onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
347
+ // covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
348
+ // non-string effectiveComponent on the web/SSR side is just as likely to be an
349
+ // ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
350
+ // wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
351
+ // reliable signal either way. Forwarding them made React DOM warn "Unknown event
352
+ // handler property" the moment any such component got an interactive modifier
353
+ // (hover:, active:, …), which is the common case, not the exception.
354
+ ...!isNative ? hasInteractive ? {
340
355
  onPointerDown: handlePointerDown,
341
356
  onPointerUp: handlePointerUp,
342
357
  onPointerLeave: handlePointerLeave,
343
- onPointerCancel: handlePointerCancel,
344
- ...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
358
+ onPointerCancel: handlePointerCancel
345
359
  } : {
346
360
  onPointerDown,
347
361
  onPointerUp,
348
362
  onPointerLeave,
349
- onPointerCancel,
350
- ...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
363
+ onPointerCancel
351
364
  } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
352
365
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
353
366
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
@@ -425,6 +438,7 @@ function cx(...classes) {
425
438
  }
426
439
  export {
427
440
  InteractiveWrapper,
441
+ KbachReset,
428
442
  ThemeContext,
429
443
  ThemeProvider,
430
444
  buildConfig,
@@ -583,6 +583,47 @@ var defaultTheme = {
583
583
  }
584
584
  };
585
585
 
586
+ // src/core/reset.ts
587
+ var RESET_STYLE_ID = "kbach-reset";
588
+ var BASE_RESET = [
589
+ // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
590
+ // border-width: 0 keeps all elements borderless by default.
591
+ "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
592
+ "body { margin: 0; padding: 0; }",
593
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
594
+ "p { margin: 0; }",
595
+ "a { color: inherit; text-decoration: none; }",
596
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
597
+ "img, video, svg { display: block; max-width: 100%; }",
598
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
599
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
600
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
601
+ // plain text. Text-like inputs, textarea, and button don't have that problem
602
+ // (their native chrome is just a skin around content utilities can fully
603
+ // restyle), so they keep the blank-canvas treatment.
604
+ "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; }",
605
+ // Native checkbox/radio/range still get typography + spacing normalized, and
606
+ // accent-color re-themes their native indicator to the current text color
607
+ // instead of the browser/OS default blue, so they stay on-brand without
608
+ // needing to be rebuilt from scratch.
609
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
610
+ // select keeps its native chrome (border/background/arrow are all part of the
611
+ // same OS-drawn widget that appearance: none would blank out) — only typography
612
+ // and spacing are normalized so it still matches surrounding text.
613
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
614
+ "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
615
+ "button, [role='button'] { cursor: pointer; }",
616
+ ":disabled { cursor: default; }",
617
+ "textarea { resize: vertical; }",
618
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
619
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
620
+ "::placeholder { opacity: 1; }",
621
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
622
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
623
+ "fieldset { padding: 0; margin: 0; }",
624
+ "table { border-collapse: collapse; border-spacing: 0; }"
625
+ ].join("\n");
626
+
586
627
  // src/core/cache.ts
587
628
  var LRUCache = class {
588
629
  constructor(capacity = 1e4, onEvict) {
@@ -2626,46 +2667,6 @@ function findOuterColon(s) {
2626
2667
  return -1;
2627
2668
  }
2628
2669
 
2629
- // src/core/reset.ts
2630
- var BASE_RESET = [
2631
- // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
2632
- // border-width: 0 keeps all elements borderless by default.
2633
- "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
2634
- "body { margin: 0; padding: 0; }",
2635
- "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2636
- "p { margin: 0; }",
2637
- "a { color: inherit; text-decoration: none; }",
2638
- "ul, ol { margin: 0; padding: 0; list-style: none; }",
2639
- "img, video, svg { display: block; max-width: 100%; }",
2640
- // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2641
- // stripping it hides their native checkmark/arrow with nothing rendered in its
2642
- // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2643
- // plain text. Text-like inputs, textarea, and button don't have that problem
2644
- // (their native chrome is just a skin around content utilities can fully
2645
- // restyle), so they keep the blank-canvas treatment.
2646
- "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; }",
2647
- // Native checkbox/radio/range still get typography + spacing normalized, and
2648
- // accent-color re-themes their native indicator to the current text color
2649
- // instead of the browser/OS default blue, so they stay on-brand without
2650
- // needing to be rebuilt from scratch.
2651
- "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2652
- // select keeps its native chrome (border/background/arrow are all part of the
2653
- // same OS-drawn widget that appearance: none would blank out) — only typography
2654
- // and spacing are normalized so it still matches surrounding text.
2655
- "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2656
- "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2657
- "button, [role='button'] { cursor: pointer; }",
2658
- ":disabled { cursor: default; }",
2659
- "textarea { resize: vertical; }",
2660
- // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2661
- // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2662
- "::placeholder { opacity: 1; }",
2663
- "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2664
- "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2665
- "fieldset { padding: 0; margin: 0; }",
2666
- "table { border-collapse: collapse; border-spacing: 0; }"
2667
- ].join("\n");
2668
-
2669
2670
  // src/core/responsiveStore.ts
2670
2671
  var RESPONSIVE_KEY = "__kbach_responsive__";
2671
2672
  function getStore() {
@@ -2774,7 +2775,7 @@ function getGlobalStyleEl() {
2774
2775
  }
2775
2776
  function injectGlobalStyles(theme) {
2776
2777
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2777
- const rules = [BASE_RESET];
2778
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2778
2779
  const ff = theme.fontFamily;
2779
2780
  if (ff?.sans && ff.sans !== "System") {
2780
2781
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3390,19 +3391,24 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
3390
3391
  ...checked !== void 0 ? { checked } : {},
3391
3392
  style: finalStyle,
3392
3393
  ...!isNative && className ? { className } : {},
3393
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
3394
- // and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
3395
- // component (react-native-web components accept both prop names), never when it's a
3396
- // literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
3397
- // property" for onPressIn/onPressOut on a host element even when the value is
3398
- // undefined — the check is on the prop KEY being present at all, not its value — so
3399
- // the key must be omitted entirely for string components, not just set to undefined.
3394
+ // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
3395
+ // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
3396
+ // Component isn't a literal HTML tag string. A non-string Component on web is just as
3397
+ // likely to be an ordinary web component (React Router's <Link>, Next.js's <Link>, any
3398
+ // custom wrapper) as an actual react-native-web primitive — "not a string" alone was never
3399
+ // a reliable signal that onPressIn/onPressOut are wanted, and forwarding them
3400
+ // unconditionally made React DOM warn "Unknown event handler property" the moment any web
3401
+ // component got an interactive modifier (hover:, active:, …), which is the common case,
3402
+ // not the exception. (A real react-native-web primitive would additionally have this pair
3403
+ // of props silently vanish after hydration anyway, once the browser-only web-substitution
3404
+ // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
3405
+ // only have traded one prop-mismatch warning for another.) Genuinely native code paths
3406
+ // still get them via the isNative branch below.
3400
3407
  ...!isNative ? {
3401
3408
  onPointerDown: handlePointerDown,
3402
3409
  onPointerUp: handlePointerUp,
3403
3410
  onPointerLeave: handlePointerLeave,
3404
- onPointerCancel: handlePointerCancel,
3405
- ...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
3411
+ onPointerCancel: handlePointerCancel
3406
3412
  } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3407
3413
  onMouseEnter: handleMouseEnter,
3408
3414
  onMouseLeave: handleMouseLeave,
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-35CBHSAG.mjs";
7
- import "./chunk-XJFOY7HN.mjs";
6
+ } from "./chunk-Z5SUFJ53.mjs";
7
+ import "./chunk-OWJUMH2V.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -584,6 +584,47 @@ var defaultTheme = {
584
584
  }
585
585
  };
586
586
 
587
+ // src/core/reset.ts
588
+ var RESET_STYLE_ID = "kbach-reset";
589
+ var BASE_RESET = [
590
+ // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
591
+ // border-width: 0 keeps all elements borderless by default.
592
+ "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
593
+ "body { margin: 0; padding: 0; }",
594
+ "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
595
+ "p { margin: 0; }",
596
+ "a { color: inherit; text-decoration: none; }",
597
+ "ul, ol { margin: 0; padding: 0; list-style: none; }",
598
+ "img, video, svg { display: block; max-width: 100%; }",
599
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
600
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
601
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
602
+ // plain text. Text-like inputs, textarea, and button don't have that problem
603
+ // (their native chrome is just a skin around content utilities can fully
604
+ // restyle), so they keep the blank-canvas treatment.
605
+ "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; }",
606
+ // Native checkbox/radio/range still get typography + spacing normalized, and
607
+ // accent-color re-themes their native indicator to the current text color
608
+ // instead of the browser/OS default blue, so they stay on-brand without
609
+ // needing to be rebuilt from scratch.
610
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
611
+ // select keeps its native chrome (border/background/arrow are all part of the
612
+ // same OS-drawn widget that appearance: none would blank out) — only typography
613
+ // and spacing are normalized so it still matches surrounding text.
614
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
615
+ "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
616
+ "button, [role='button'] { cursor: pointer; }",
617
+ ":disabled { cursor: default; }",
618
+ "textarea { resize: vertical; }",
619
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
620
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
621
+ "::placeholder { opacity: 1; }",
622
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
623
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
624
+ "fieldset { padding: 0; margin: 0; }",
625
+ "table { border-collapse: collapse; border-spacing: 0; }"
626
+ ].join("\n");
627
+
587
628
  // src/core/cache.ts
588
629
  var LRUCache = class {
589
630
  constructor(capacity = 1e4, onEvict) {
@@ -2627,46 +2668,6 @@ function findOuterColon(s) {
2627
2668
  return -1;
2628
2669
  }
2629
2670
 
2630
- // src/core/reset.ts
2631
- var BASE_RESET = [
2632
- // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
2633
- // border-width: 0 keeps all elements borderless by default.
2634
- "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }",
2635
- "body { margin: 0; padding: 0; }",
2636
- "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }",
2637
- "p { margin: 0; }",
2638
- "a { color: inherit; text-decoration: none; }",
2639
- "ul, ol { margin: 0; padding: 0; list-style: none; }",
2640
- "img, video, svg { display: block; max-width: 100%; }",
2641
- // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2642
- // stripping it hides their native checkmark/arrow with nothing rendered in its
2643
- // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2644
- // plain text. Text-like inputs, textarea, and button don't have that problem
2645
- // (their native chrome is just a skin around content utilities can fully
2646
- // restyle), so they keep the blank-canvas treatment.
2647
- "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; }",
2648
- // Native checkbox/radio/range still get typography + spacing normalized, and
2649
- // accent-color re-themes their native indicator to the current text color
2650
- // instead of the browser/OS default blue, so they stay on-brand without
2651
- // needing to be rebuilt from scratch.
2652
- "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2653
- // select keeps its native chrome (border/background/arrow are all part of the
2654
- // same OS-drawn widget that appearance: none would blank out) — only typography
2655
- // and spacing are normalized so it still matches surrounding text.
2656
- "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2657
- "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2658
- "button, [role='button'] { cursor: pointer; }",
2659
- ":disabled { cursor: default; }",
2660
- "textarea { resize: vertical; }",
2661
- // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2662
- // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2663
- "::placeholder { opacity: 1; }",
2664
- "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2665
- "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2666
- "fieldset { padding: 0; margin: 0; }",
2667
- "table { border-collapse: collapse; border-spacing: 0; }"
2668
- ].join("\n");
2669
-
2670
2671
  // src/core/responsiveStore.ts
2671
2672
  var RESPONSIVE_KEY = "__kbach_responsive__";
2672
2673
  function getStore() {
@@ -2775,7 +2776,7 @@ function getGlobalStyleEl() {
2775
2776
  }
2776
2777
  function injectGlobalStyles(theme) {
2777
2778
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2778
- const rules = [BASE_RESET];
2779
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2779
2780
  const ff = theme.fontFamily;
2780
2781
  if (ff?.sans && ff.sans !== "System") {
2781
2782
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -3391,19 +3392,24 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
3391
3392
  ...checked !== void 0 ? { checked } : {},
3392
3393
  style: finalStyle,
3393
3394
  ...!isNative && className ? { className } : {},
3394
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
3395
- // and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
3396
- // component (react-native-web components accept both prop names), never when it's a
3397
- // literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
3398
- // property" for onPressIn/onPressOut on a host element even when the value is
3399
- // undefined — the check is on the prop KEY being present at all, not its value — so
3400
- // the key must be omitted entirely for string components, not just set to undefined.
3395
+ // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
3396
+ // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
3397
+ // Component isn't a literal HTML tag string. A non-string Component on web is just as
3398
+ // likely to be an ordinary web component (React Router's <Link>, Next.js's <Link>, any
3399
+ // custom wrapper) as an actual react-native-web primitive — "not a string" alone was never
3400
+ // a reliable signal that onPressIn/onPressOut are wanted, and forwarding them
3401
+ // unconditionally made React DOM warn "Unknown event handler property" the moment any web
3402
+ // component got an interactive modifier (hover:, active:, …), which is the common case,
3403
+ // not the exception. (A real react-native-web primitive would additionally have this pair
3404
+ // of props silently vanish after hydration anyway, once the browser-only web-substitution
3405
+ // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
3406
+ // only have traded one prop-mismatch warning for another.) Genuinely native code paths
3407
+ // still get them via the isNative branch below.
3401
3408
  ...!isNative ? {
3402
3409
  onPointerDown: handlePointerDown,
3403
3410
  onPointerUp: handlePointerUp,
3404
3411
  onPointerLeave: handlePointerLeave,
3405
- onPointerCancel: handlePointerCancel,
3406
- ...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
3412
+ onPointerCancel: handlePointerCancel
3407
3413
  } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3408
3414
  onMouseEnter: handleMouseEnter,
3409
3415
  onMouseLeave: handleMouseLeave,
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-35CBHSAG.mjs";
7
+ } from "./chunk-Z5SUFJ53.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-XJFOY7HN.mjs";
10
+ } from "./chunk-OWJUMH2V.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,
@@ -60,6 +60,7 @@ function escapeCSSSelector(cls) {
60
60
  }
61
61
 
62
62
  // src/core/reset.ts
63
+ var RESET_STYLE_ID = "kbach-reset";
63
64
  var BASE_RESET = [
64
65
  // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
65
66
  // border-width: 0 keeps all elements borderless by default.
@@ -2677,7 +2678,7 @@ function getGlobalStyleEl() {
2677
2678
  }
2678
2679
  function injectGlobalStyles(theme) {
2679
2680
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2680
- const rules = [BASE_RESET];
2681
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2681
2682
  const ff = theme.fontFamily;
2682
2683
  if (ff?.sans && ff.sans !== "System") {
2683
2684
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
@@ -26,6 +26,7 @@ function escapeCSSSelector(cls) {
26
26
  }
27
27
 
28
28
  // src/core/reset.ts
29
+ var RESET_STYLE_ID = "kbach-reset";
29
30
  var BASE_RESET = [
30
31
  // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
31
32
  // border-width: 0 keeps all elements borderless by default.
@@ -2643,7 +2644,7 @@ function getGlobalStyleEl() {
2643
2644
  }
2644
2645
  function injectGlobalStyles(theme) {
2645
2646
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2646
- const rules = [BASE_RESET];
2647
+ const rules = document.getElementById(RESET_STYLE_ID) ? [] : [BASE_RESET];
2647
2648
  const ff = theme.fontFamily;
2648
2649
  if (ff?.sans && ff.sans !== "System") {
2649
2650
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
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",