@kbach/react 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -72
- package/dist/{chunk-ZZWJK33J.mjs → chunk-BAUO36PM.mjs} +64 -6
- package/dist/{chunk-4A24NQ5X.mjs → chunk-FXDK5Z6V.mjs} +1 -1
- package/dist/index.js +83 -8
- package/dist/index.mjs +20 -3
- package/dist/jsx-dev-runtime.js +64 -6
- package/dist/jsx-dev-runtime.mjs +2 -2
- package/dist/jsx-runtime.js +64 -6
- package/dist/jsx-runtime.mjs +2 -2
- package/dist/vite-plugin.js +112 -3
- package/dist/vite-plugin.mjs +112 -3
- package/kbach-react.md +23 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,11 @@ 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:
|
|
24
|
+
|
|
23
25
|
### 1. Configure the JSX runtime
|
|
24
26
|
|
|
25
|
-
**tsconfig.json**
|
|
27
|
+
**tsconfig.json** (works for Vite, Next.js, React Router, and most other tooling — it's the one setting that everything reads):
|
|
26
28
|
|
|
27
29
|
```json
|
|
28
30
|
{
|
|
@@ -33,121 +35,87 @@ npm install @kbach/react
|
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
Don't also pass `jsxImportSource` to `@vitejs/plugin-react` (or any other bundler plugin) unless you have a specific reason to — the tsconfig setting alone is enough, and pointing two different places at the JSX runtime is a common source of "it's not applying" confusion (or, worse, plugin conflicts — see the React Router note below).
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
/** @jsxImportSource @kbach/react */
|
|
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
|
-
|
|
42
|
+
### 2. Wrap your app
|
|
43
43
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
```jsx
|
|
45
|
+
import { ThemeProvider } from '@kbach/react';
|
|
46
|
+
|
|
47
|
+
export default function Root() {
|
|
48
|
+
return (
|
|
49
|
+
<ThemeProvider defaultMode="system">
|
|
50
|
+
<App />
|
|
51
|
+
</ThemeProvider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
|
|
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.
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
### Optional: static CSS for production (Vite only)
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
npm install -D vite @vitejs/plugin-react
|
|
59
|
-
```
|
|
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:
|
|
60
61
|
|
|
61
62
|
```ts
|
|
62
63
|
// vite.config.ts
|
|
63
64
|
import { defineConfig } from 'vite';
|
|
64
|
-
import react from '@vitejs/plugin-react';
|
|
65
65
|
import { kbach } from '@kbach/react/vite';
|
|
66
66
|
|
|
67
67
|
export default defineConfig({
|
|
68
68
|
plugins: [
|
|
69
|
-
|
|
69
|
+
// ...your framework's own plugin(s) — see the framework notes below
|
|
70
|
+
// before adding @vitejs/plugin-react yourself.
|
|
70
71
|
kbach(),
|
|
71
72
|
],
|
|
72
73
|
});
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### 3. Create and import kbach.css
|
|
78
|
-
|
|
79
|
-
Create `kbach.css` anywhere in your project (e.g. `src/kbach.css`):
|
|
76
|
+
Then create an empty `kbach.css` anywhere in your project and import it once in your app entry:
|
|
80
77
|
|
|
81
78
|
```css
|
|
79
|
+
/* src/kbach.css */
|
|
82
80
|
/* kbach:start */
|
|
83
81
|
/* kbach:end */
|
|
84
82
|
```
|
|
85
83
|
|
|
86
|
-
Import it in your app entry:
|
|
87
|
-
|
|
88
84
|
```ts
|
|
89
85
|
// main.tsx
|
|
90
86
|
import './kbach.css';
|
|
91
87
|
```
|
|
92
88
|
|
|
93
|
-
Importing `kbach.css` automatically disables runtime
|
|
94
|
-
|
|
95
|
-
### 4. Wrap your app
|
|
89
|
+
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.
|
|
96
90
|
|
|
97
|
-
|
|
98
|
-
import { ThemeProvider } from '@kbach/react';
|
|
99
|
-
|
|
100
|
-
export default function Root() {
|
|
101
|
-
return (
|
|
102
|
-
<ThemeProvider defaultMode="system">
|
|
103
|
-
<App />
|
|
104
|
-
</ThemeProvider>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
91
|
+
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.
|
|
108
92
|
|
|
109
93
|
## Next.js
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Two things differ from the Vite setup:
|
|
95
|
+
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.
|
|
114
96
|
|
|
115
|
-
|
|
97
|
+
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.
|
|
116
98
|
|
|
117
|
-
|
|
99
|
+
## React Router
|
|
118
100
|
|
|
119
|
-
|
|
120
|
-
// app/providers.tsx
|
|
121
|
-
'use client';
|
|
122
|
-
import { ThemeProvider } from '@kbach/react';
|
|
101
|
+
**Framework mode (v7+, SSR by default):** `@react-router/dev`'s own `reactRouter()` Vite plugin already includes its own React JSX transform and Fast Refresh integration — **do not also add `@vitejs/plugin-react`.** Having both active makes each one inject its own Fast Refresh preamble into the same module, which crashes the page at runtime (`Identifier 'RefreshRuntime' has already been declared`) before React ever hydrates — every class on the page will silently fail to style because the app never actually mounts. The correct setup is just:
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```tsx
|
|
130
|
-
// app/layout.tsx (Server Component — no 'use client' needed here)
|
|
131
|
-
import { Providers } from './providers';
|
|
103
|
+
```ts
|
|
104
|
+
// vite.config.ts
|
|
105
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
106
|
+
import { defineConfig } from 'vite';
|
|
107
|
+
import { kbach } from '@kbach/react/vite'; // omit if you're not using static CSS
|
|
132
108
|
|
|
133
|
-
export default
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<body>
|
|
137
|
-
<Providers>{children}</Providers>
|
|
138
|
-
</body>
|
|
139
|
-
</html>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
109
|
+
export default defineConfig({
|
|
110
|
+
plugins: [kbach(), reactRouter()],
|
|
111
|
+
});
|
|
142
112
|
```
|
|
143
113
|
|
|
144
|
-
|
|
114
|
+
`jsxImportSource` in `tsconfig.json` (step 1 above) is all that's needed for the JSX runtime — React Router's Vite plugin reads it the same way plain Vite does. `include`'s default scan dirs (`['src', 'app', 'pages', 'components', 'views', 'layouts']`) already cover React Router's `app/` routes convention if you're using the static-CSS plugin.
|
|
145
115
|
|
|
146
|
-
|
|
116
|
+
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.
|
|
147
117
|
|
|
148
|
-
**
|
|
149
|
-
|
|
150
|
-
**Library mode (client-only, `createBrowserRouter`/`<BrowserRouter>`):** no SSR involved — set it up exactly like any other Vite + React app per the steps above.
|
|
118
|
+
**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).
|
|
151
119
|
|
|
152
120
|
## API
|
|
153
121
|
|
|
@@ -332,6 +300,17 @@ Negated: `not-hover:`, `not-focus:`, `not-pressed:`, `not-active:`, `not-disable
|
|
|
332
300
|
<p className="peer-focus:text-blue-6" />
|
|
333
301
|
```
|
|
334
302
|
|
|
303
|
+
Nested groups need names, or the inner element reacts to whichever `.group` is nearest — not necessarily the one you meant:
|
|
304
|
+
|
|
305
|
+
```jsx
|
|
306
|
+
<div className="group/card">
|
|
307
|
+
<div className="group/icon">
|
|
308
|
+
<span className="group-hover/icon:opacity-100" /> {/* only the inner group */}
|
|
309
|
+
</div>
|
|
310
|
+
<span className="group-hover/card:underline" /> {/* only the outer group */}
|
|
311
|
+
</div>
|
|
312
|
+
```
|
|
313
|
+
|
|
335
314
|
### Responsive
|
|
336
315
|
|
|
337
316
|
| Modifier | Min-width |
|
|
@@ -650,11 +650,30 @@ function clearPluginModifiers() {
|
|
|
650
650
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
651
651
|
_invalidate();
|
|
652
652
|
}
|
|
653
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
654
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
655
|
+
function getNamedGroupPeerModifier(name) {
|
|
656
|
+
const cached = _namedModifierCache.get(name);
|
|
657
|
+
if (cached) return cached;
|
|
658
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
659
|
+
if (!m) return void 0;
|
|
660
|
+
const [, kind, trigger, groupName] = m;
|
|
661
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
662
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
663
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
664
|
+
const def = {
|
|
665
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
666
|
+
jsBehavior: "css-only",
|
|
667
|
+
forcesImportant: true
|
|
668
|
+
};
|
|
669
|
+
_namedModifierCache.set(name, def);
|
|
670
|
+
return def;
|
|
671
|
+
}
|
|
653
672
|
function getModifier(name) {
|
|
654
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
673
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
655
674
|
}
|
|
656
675
|
function isKnownModifier(name) {
|
|
657
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
676
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
658
677
|
}
|
|
659
678
|
function _allEntries() {
|
|
660
679
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2001,6 +2020,33 @@ var RESOLVERS = {
|
|
|
2001
2020
|
const color = resolveColor(value, colors, isArbitrary);
|
|
2002
2021
|
return color ? { accentColor: color } : null;
|
|
2003
2022
|
},
|
|
2023
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
2024
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
2025
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
2026
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
2027
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
2028
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
2029
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
2030
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2031
|
+
if (!value) return null;
|
|
2032
|
+
if (value === "none") return { stroke: "none" };
|
|
2033
|
+
if (isArbitrary) {
|
|
2034
|
+
const w = toNativeValue(value);
|
|
2035
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
2036
|
+
}
|
|
2037
|
+
const color = resolveColor(value, colors, false);
|
|
2038
|
+
if (color) return { stroke: color };
|
|
2039
|
+
const n = parseFloat(value);
|
|
2040
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
2041
|
+
},
|
|
2042
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
2043
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2044
|
+
if (!value) return null;
|
|
2045
|
+
if (value === "none") return { fill: "none" };
|
|
2046
|
+
if (isArbitrary) return { fill: value };
|
|
2047
|
+
const color = resolveColor(value, colors, false);
|
|
2048
|
+
return color ? { fill: color } : null;
|
|
2049
|
+
},
|
|
2004
2050
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
2005
2051
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
2006
2052
|
if (!value) return { display: "flex" };
|
|
@@ -2304,10 +2350,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2304
2350
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2305
2351
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2306
2352
|
}
|
|
2353
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2307
2354
|
function resolveUtility(parsed, theme) {
|
|
2308
2355
|
if (!parsed.value) {
|
|
2309
2356
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2310
2357
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2358
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2311
2359
|
}
|
|
2312
2360
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2313
2361
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2325,7 +2373,7 @@ function getPluginStandaloneMap() {
|
|
|
2325
2373
|
return PLUGIN_STANDALONE;
|
|
2326
2374
|
}
|
|
2327
2375
|
function isKnownUtility(utility) {
|
|
2328
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2376
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2329
2377
|
}
|
|
2330
2378
|
var _sortedPrefixes = null;
|
|
2331
2379
|
var _standaloneNames = null;
|
|
@@ -3604,9 +3652,19 @@ var InteractiveWrapper = forwardRef(
|
|
|
3604
3652
|
style: finalStyle,
|
|
3605
3653
|
...!isNative && className ? { className } : {},
|
|
3606
3654
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3607
|
-
//
|
|
3608
|
-
// (
|
|
3609
|
-
|
|
3655
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3656
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3657
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3658
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3659
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3660
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3661
|
+
...!isNative ? {
|
|
3662
|
+
onPointerDown: handlePointerDown,
|
|
3663
|
+
onPointerUp: handlePointerUp,
|
|
3664
|
+
onPointerLeave: handlePointerLeave,
|
|
3665
|
+
onPointerCancel: handlePointerCancel,
|
|
3666
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3667
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3610
3668
|
onMouseEnter: handleMouseEnter,
|
|
3611
3669
|
onMouseLeave: handleMouseLeave,
|
|
3612
3670
|
onFocus: handleFocus,
|
package/dist/index.js
CHANGED
|
@@ -775,11 +775,30 @@ function clearPluginModifiers() {
|
|
|
775
775
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
776
776
|
_invalidate();
|
|
777
777
|
}
|
|
778
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
779
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
780
|
+
function getNamedGroupPeerModifier(name) {
|
|
781
|
+
const cached = _namedModifierCache.get(name);
|
|
782
|
+
if (cached) return cached;
|
|
783
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
784
|
+
if (!m) return void 0;
|
|
785
|
+
const [, kind, trigger, groupName] = m;
|
|
786
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
787
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
788
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
789
|
+
const def = {
|
|
790
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
791
|
+
jsBehavior: "css-only",
|
|
792
|
+
forcesImportant: true
|
|
793
|
+
};
|
|
794
|
+
_namedModifierCache.set(name, def);
|
|
795
|
+
return def;
|
|
796
|
+
}
|
|
778
797
|
function getModifier(name) {
|
|
779
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
798
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
780
799
|
}
|
|
781
800
|
function isKnownModifier(name) {
|
|
782
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
801
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
783
802
|
}
|
|
784
803
|
function _allEntries() {
|
|
785
804
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2120,6 +2139,33 @@ var RESOLVERS = {
|
|
|
2120
2139
|
const color = resolveColor(value, colors, isArbitrary);
|
|
2121
2140
|
return color ? { accentColor: color } : null;
|
|
2122
2141
|
},
|
|
2142
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
2143
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
2144
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
2145
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
2146
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
2147
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
2148
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
2149
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2150
|
+
if (!value) return null;
|
|
2151
|
+
if (value === "none") return { stroke: "none" };
|
|
2152
|
+
if (isArbitrary) {
|
|
2153
|
+
const w = toNativeValue(value);
|
|
2154
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
2155
|
+
}
|
|
2156
|
+
const color = resolveColor(value, colors, false);
|
|
2157
|
+
if (color) return { stroke: color };
|
|
2158
|
+
const n = parseFloat(value);
|
|
2159
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
2160
|
+
},
|
|
2161
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
2162
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2163
|
+
if (!value) return null;
|
|
2164
|
+
if (value === "none") return { fill: "none" };
|
|
2165
|
+
if (isArbitrary) return { fill: value };
|
|
2166
|
+
const color = resolveColor(value, colors, false);
|
|
2167
|
+
return color ? { fill: color } : null;
|
|
2168
|
+
},
|
|
2123
2169
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
2124
2170
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
2125
2171
|
if (!value) return { display: "flex" };
|
|
@@ -2423,10 +2469,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2423
2469
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2424
2470
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2425
2471
|
}
|
|
2472
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2426
2473
|
function resolveUtility(parsed, theme) {
|
|
2427
2474
|
if (!parsed.value) {
|
|
2428
2475
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2429
2476
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2477
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2430
2478
|
}
|
|
2431
2479
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2432
2480
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2444,7 +2492,7 @@ function getPluginStandaloneMap() {
|
|
|
2444
2492
|
return PLUGIN_STANDALONE;
|
|
2445
2493
|
}
|
|
2446
2494
|
function isKnownUtility(utility) {
|
|
2447
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2495
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2448
2496
|
}
|
|
2449
2497
|
var _sortedPrefixes = null;
|
|
2450
2498
|
var _standaloneNames = null;
|
|
@@ -3890,8 +3938,25 @@ function styled(Component, baseClasses = "") {
|
|
|
3890
3938
|
// Only attach state-tracking handlers when interactive modifiers are present.
|
|
3891
3939
|
// Always forward user-provided handlers to avoid silently swallowing them.
|
|
3892
3940
|
// onPressIn/onPressOut are forwarded on web too (not just native) since
|
|
3893
|
-
// react-native-web components rely on receiving them directly
|
|
3894
|
-
|
|
3941
|
+
// react-native-web components rely on receiving them directly — but only
|
|
3942
|
+
// when effectiveComponent is an actual component, never a literal HTML tag
|
|
3943
|
+
// string ('div', 'a', …). React DOM warns "Unknown event handler property"
|
|
3944
|
+
// for onPressIn/onPressOut on a host element even when the value is
|
|
3945
|
+
// undefined — the check is on the prop KEY being present at all, not its
|
|
3946
|
+
// value — so the key must be omitted entirely for string components.
|
|
3947
|
+
...isWeb ? hasInteractive ? {
|
|
3948
|
+
onPointerDown: handlePointerDown,
|
|
3949
|
+
onPointerUp: handlePointerUp,
|
|
3950
|
+
onPointerLeave: handlePointerLeave,
|
|
3951
|
+
onPointerCancel: handlePointerCancel,
|
|
3952
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
3953
|
+
} : {
|
|
3954
|
+
onPointerDown,
|
|
3955
|
+
onPointerUp,
|
|
3956
|
+
onPointerLeave,
|
|
3957
|
+
onPointerCancel,
|
|
3958
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
3959
|
+
} : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
|
|
3895
3960
|
onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
|
|
3896
3961
|
onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
|
|
3897
3962
|
onFocus: hasInteractive ? handleFocus : onFocus,
|
|
@@ -4012,9 +4077,19 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
|
|
|
4012
4077
|
style: finalStyle,
|
|
4013
4078
|
...!isNative && className ? { className } : {},
|
|
4014
4079
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
4015
|
-
//
|
|
4016
|
-
// (
|
|
4017
|
-
|
|
4080
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
4081
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
4082
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
4083
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
4084
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
4085
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
4086
|
+
...!isNative ? {
|
|
4087
|
+
onPointerDown: handlePointerDown,
|
|
4088
|
+
onPointerUp: handlePointerUp,
|
|
4089
|
+
onPointerLeave: handlePointerLeave,
|
|
4090
|
+
onPointerCancel: handlePointerCancel,
|
|
4091
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
4092
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
4018
4093
|
onMouseEnter: handleMouseEnter,
|
|
4019
4094
|
onMouseLeave: handleMouseLeave,
|
|
4020
4095
|
onFocus: handleFocus,
|
package/dist/index.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
useGlobalDarkMode,
|
|
37
37
|
useGlobalWidth,
|
|
38
38
|
useSyncExternalStore
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-BAUO36PM.mjs";
|
|
40
40
|
|
|
41
41
|
// src/context.tsx
|
|
42
42
|
import { createContext, useContext } from "react";
|
|
@@ -330,8 +330,25 @@ function styled(Component, baseClasses = "") {
|
|
|
330
330
|
// Only attach state-tracking handlers when interactive modifiers are present.
|
|
331
331
|
// Always forward user-provided handlers to avoid silently swallowing them.
|
|
332
332
|
// onPressIn/onPressOut are forwarded on web too (not just native) since
|
|
333
|
-
// react-native-web components rely on receiving them directly
|
|
334
|
-
|
|
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 ? {
|
|
340
|
+
onPointerDown: handlePointerDown,
|
|
341
|
+
onPointerUp: handlePointerUp,
|
|
342
|
+
onPointerLeave: handlePointerLeave,
|
|
343
|
+
onPointerCancel: handlePointerCancel,
|
|
344
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
345
|
+
} : {
|
|
346
|
+
onPointerDown,
|
|
347
|
+
onPointerUp,
|
|
348
|
+
onPointerLeave,
|
|
349
|
+
onPointerCancel,
|
|
350
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
351
|
+
} : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
|
|
335
352
|
onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
|
|
336
353
|
onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
|
|
337
354
|
onFocus: hasInteractive ? handleFocus : onFocus,
|
package/dist/jsx-dev-runtime.js
CHANGED
|
@@ -728,11 +728,30 @@ function clearPluginModifiers() {
|
|
|
728
728
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
729
729
|
_invalidate();
|
|
730
730
|
}
|
|
731
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
732
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
733
|
+
function getNamedGroupPeerModifier(name) {
|
|
734
|
+
const cached = _namedModifierCache.get(name);
|
|
735
|
+
if (cached) return cached;
|
|
736
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
737
|
+
if (!m) return void 0;
|
|
738
|
+
const [, kind, trigger, groupName] = m;
|
|
739
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
740
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
741
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
742
|
+
const def = {
|
|
743
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
744
|
+
jsBehavior: "css-only",
|
|
745
|
+
forcesImportant: true
|
|
746
|
+
};
|
|
747
|
+
_namedModifierCache.set(name, def);
|
|
748
|
+
return def;
|
|
749
|
+
}
|
|
731
750
|
function getModifier(name) {
|
|
732
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
751
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
733
752
|
}
|
|
734
753
|
function isKnownModifier(name) {
|
|
735
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
754
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
736
755
|
}
|
|
737
756
|
function _allEntries() {
|
|
738
757
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2079,6 +2098,33 @@ var RESOLVERS = {
|
|
|
2079
2098
|
const color = resolveColor(value, colors, isArbitrary);
|
|
2080
2099
|
return color ? { accentColor: color } : null;
|
|
2081
2100
|
},
|
|
2101
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
2102
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
2103
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
2104
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
2105
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
2106
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
2107
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
2108
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2109
|
+
if (!value) return null;
|
|
2110
|
+
if (value === "none") return { stroke: "none" };
|
|
2111
|
+
if (isArbitrary) {
|
|
2112
|
+
const w = toNativeValue(value);
|
|
2113
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
2114
|
+
}
|
|
2115
|
+
const color = resolveColor(value, colors, false);
|
|
2116
|
+
if (color) return { stroke: color };
|
|
2117
|
+
const n = parseFloat(value);
|
|
2118
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
2119
|
+
},
|
|
2120
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
2121
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2122
|
+
if (!value) return null;
|
|
2123
|
+
if (value === "none") return { fill: "none" };
|
|
2124
|
+
if (isArbitrary) return { fill: value };
|
|
2125
|
+
const color = resolveColor(value, colors, false);
|
|
2126
|
+
return color ? { fill: color } : null;
|
|
2127
|
+
},
|
|
2082
2128
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
2083
2129
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
2084
2130
|
if (!value) return { display: "flex" };
|
|
@@ -2382,10 +2428,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2382
2428
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2383
2429
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2384
2430
|
}
|
|
2431
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2385
2432
|
function resolveUtility(parsed, theme) {
|
|
2386
2433
|
if (!parsed.value) {
|
|
2387
2434
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2388
2435
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2436
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2389
2437
|
}
|
|
2390
2438
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2391
2439
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2403,7 +2451,7 @@ function getPluginStandaloneMap() {
|
|
|
2403
2451
|
return PLUGIN_STANDALONE;
|
|
2404
2452
|
}
|
|
2405
2453
|
function isKnownUtility(utility) {
|
|
2406
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2454
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2407
2455
|
}
|
|
2408
2456
|
var _sortedPrefixes = null;
|
|
2409
2457
|
var _standaloneNames = null;
|
|
@@ -3301,9 +3349,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3301
3349
|
style: finalStyle,
|
|
3302
3350
|
...!isNative && className ? { className } : {},
|
|
3303
3351
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3304
|
-
//
|
|
3305
|
-
// (
|
|
3306
|
-
|
|
3352
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3353
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3354
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3355
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3356
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3357
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3358
|
+
...!isNative ? {
|
|
3359
|
+
onPointerDown: handlePointerDown,
|
|
3360
|
+
onPointerUp: handlePointerUp,
|
|
3361
|
+
onPointerLeave: handlePointerLeave,
|
|
3362
|
+
onPointerCancel: handlePointerCancel,
|
|
3363
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3364
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3307
3365
|
onMouseEnter: handleMouseEnter,
|
|
3308
3366
|
onMouseLeave: handleMouseLeave,
|
|
3309
3367
|
onFocus: handleFocus,
|
package/dist/jsx-dev-runtime.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
Fragment,
|
|
4
4
|
jsx,
|
|
5
5
|
jsxs
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-FXDK5Z6V.mjs";
|
|
7
|
+
import "./chunk-BAUO36PM.mjs";
|
|
8
8
|
|
|
9
9
|
// src/jsx-dev-runtime.tsx
|
|
10
10
|
function jsxDEV(type, props, key, isStaticChildren, source, self) {
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -729,11 +729,30 @@ function clearPluginModifiers() {
|
|
|
729
729
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
730
730
|
_invalidate();
|
|
731
731
|
}
|
|
732
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
733
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
734
|
+
function getNamedGroupPeerModifier(name) {
|
|
735
|
+
const cached = _namedModifierCache.get(name);
|
|
736
|
+
if (cached) return cached;
|
|
737
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
738
|
+
if (!m) return void 0;
|
|
739
|
+
const [, kind, trigger, groupName] = m;
|
|
740
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
741
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
742
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
743
|
+
const def = {
|
|
744
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
745
|
+
jsBehavior: "css-only",
|
|
746
|
+
forcesImportant: true
|
|
747
|
+
};
|
|
748
|
+
_namedModifierCache.set(name, def);
|
|
749
|
+
return def;
|
|
750
|
+
}
|
|
732
751
|
function getModifier(name) {
|
|
733
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
752
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
734
753
|
}
|
|
735
754
|
function isKnownModifier(name) {
|
|
736
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
755
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
737
756
|
}
|
|
738
757
|
function _allEntries() {
|
|
739
758
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2080,6 +2099,33 @@ var RESOLVERS = {
|
|
|
2080
2099
|
const color = resolveColor(value, colors, isArbitrary);
|
|
2081
2100
|
return color ? { accentColor: color } : null;
|
|
2082
2101
|
},
|
|
2102
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
2103
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
2104
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
2105
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
2106
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
2107
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
2108
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
2109
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2110
|
+
if (!value) return null;
|
|
2111
|
+
if (value === "none") return { stroke: "none" };
|
|
2112
|
+
if (isArbitrary) {
|
|
2113
|
+
const w = toNativeValue(value);
|
|
2114
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
2115
|
+
}
|
|
2116
|
+
const color = resolveColor(value, colors, false);
|
|
2117
|
+
if (color) return { stroke: color };
|
|
2118
|
+
const n = parseFloat(value);
|
|
2119
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
2120
|
+
},
|
|
2121
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
2122
|
+
if (!getEffectiveIsWeb()) return null;
|
|
2123
|
+
if (!value) return null;
|
|
2124
|
+
if (value === "none") return { fill: "none" };
|
|
2125
|
+
if (isArbitrary) return { fill: value };
|
|
2126
|
+
const color = resolveColor(value, colors, false);
|
|
2127
|
+
return color ? { fill: color } : null;
|
|
2128
|
+
},
|
|
2083
2129
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
2084
2130
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
2085
2131
|
if (!value) return { display: "flex" };
|
|
@@ -2383,10 +2429,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2383
2429
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2384
2430
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2385
2431
|
}
|
|
2432
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2386
2433
|
function resolveUtility(parsed, theme) {
|
|
2387
2434
|
if (!parsed.value) {
|
|
2388
2435
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2389
2436
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2437
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2390
2438
|
}
|
|
2391
2439
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2392
2440
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2404,7 +2452,7 @@ function getPluginStandaloneMap() {
|
|
|
2404
2452
|
return PLUGIN_STANDALONE;
|
|
2405
2453
|
}
|
|
2406
2454
|
function isKnownUtility(utility) {
|
|
2407
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2455
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2408
2456
|
}
|
|
2409
2457
|
var _sortedPrefixes = null;
|
|
2410
2458
|
var _standaloneNames = null;
|
|
@@ -3302,9 +3350,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3302
3350
|
style: finalStyle,
|
|
3303
3351
|
...!isNative && className ? { className } : {},
|
|
3304
3352
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3305
|
-
//
|
|
3306
|
-
// (
|
|
3307
|
-
|
|
3353
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3354
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3355
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3356
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3357
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3358
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3359
|
+
...!isNative ? {
|
|
3360
|
+
onPointerDown: handlePointerDown,
|
|
3361
|
+
onPointerUp: handlePointerUp,
|
|
3362
|
+
onPointerLeave: handlePointerLeave,
|
|
3363
|
+
onPointerCancel: handlePointerCancel,
|
|
3364
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3365
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3308
3366
|
onMouseEnter: handleMouseEnter,
|
|
3309
3367
|
onMouseLeave: handleMouseLeave,
|
|
3310
3368
|
onFocus: handleFocus,
|
package/dist/jsx-runtime.mjs
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
_invalidateDefaultFontCache,
|
|
5
5
|
jsx,
|
|
6
6
|
jsxs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-FXDK5Z6V.mjs";
|
|
8
8
|
import {
|
|
9
9
|
registerWebElement
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BAUO36PM.mjs";
|
|
11
11
|
export {
|
|
12
12
|
Fragment,
|
|
13
13
|
_invalidateDefaultFontCache,
|
package/dist/vite-plugin.js
CHANGED
|
@@ -1939,6 +1939,33 @@ var RESOLVERS = {
|
|
|
1939
1939
|
const color = resolveColor(value, colors, isArbitrary);
|
|
1940
1940
|
return color ? { accentColor: color } : null;
|
|
1941
1941
|
},
|
|
1942
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
1943
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
1944
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
1945
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
1946
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
1947
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
1948
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
1949
|
+
if (!getEffectiveIsWeb()) return null;
|
|
1950
|
+
if (!value) return null;
|
|
1951
|
+
if (value === "none") return { stroke: "none" };
|
|
1952
|
+
if (isArbitrary) {
|
|
1953
|
+
const w = toNativeValue(value);
|
|
1954
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
1955
|
+
}
|
|
1956
|
+
const color = resolveColor(value, colors, false);
|
|
1957
|
+
if (color) return { stroke: color };
|
|
1958
|
+
const n = parseFloat(value);
|
|
1959
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
1960
|
+
},
|
|
1961
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
1962
|
+
if (!getEffectiveIsWeb()) return null;
|
|
1963
|
+
if (!value) return null;
|
|
1964
|
+
if (value === "none") return { fill: "none" };
|
|
1965
|
+
if (isArbitrary) return { fill: value };
|
|
1966
|
+
const color = resolveColor(value, colors, false);
|
|
1967
|
+
return color ? { fill: color } : null;
|
|
1968
|
+
},
|
|
1942
1969
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
1943
1970
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
1944
1971
|
if (!value) return { display: "flex" };
|
|
@@ -2242,10 +2269,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2242
2269
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2243
2270
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2244
2271
|
}
|
|
2272
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2245
2273
|
function resolveUtility(parsed, theme) {
|
|
2246
2274
|
if (!parsed.value) {
|
|
2247
2275
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2248
2276
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2277
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2249
2278
|
}
|
|
2250
2279
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2251
2280
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2363,11 +2392,30 @@ function clearPluginModifiers() {
|
|
|
2363
2392
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2364
2393
|
_invalidate();
|
|
2365
2394
|
}
|
|
2395
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
2396
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
2397
|
+
function getNamedGroupPeerModifier(name) {
|
|
2398
|
+
const cached = _namedModifierCache.get(name);
|
|
2399
|
+
if (cached) return cached;
|
|
2400
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
2401
|
+
if (!m) return void 0;
|
|
2402
|
+
const [, kind, trigger, groupName] = m;
|
|
2403
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
2404
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
2405
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
2406
|
+
const def = {
|
|
2407
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
2408
|
+
jsBehavior: "css-only",
|
|
2409
|
+
forcesImportant: true
|
|
2410
|
+
};
|
|
2411
|
+
_namedModifierCache.set(name, def);
|
|
2412
|
+
return def;
|
|
2413
|
+
}
|
|
2366
2414
|
function getModifier(name) {
|
|
2367
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2415
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2368
2416
|
}
|
|
2369
2417
|
function isKnownModifier(name) {
|
|
2370
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2418
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2371
2419
|
}
|
|
2372
2420
|
|
|
2373
2421
|
// src/core/parser.ts
|
|
@@ -3176,6 +3224,38 @@ function scanDir(dir, onFile) {
|
|
|
3176
3224
|
}
|
|
3177
3225
|
}
|
|
3178
3226
|
}
|
|
3227
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
3228
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
3229
|
+
function scanCssFileInto(filePath, into) {
|
|
3230
|
+
try {
|
|
3231
|
+
const text = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
3232
|
+
let m;
|
|
3233
|
+
CSS_CLASS_SELECTOR_RE.lastIndex = 0;
|
|
3234
|
+
while ((m = CSS_CLASS_SELECTOR_RE.exec(text)) !== null) into.add(m[1]);
|
|
3235
|
+
} catch {
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
function scanCssSelectorsInto(dir, into) {
|
|
3239
|
+
let entries;
|
|
3240
|
+
try {
|
|
3241
|
+
entries = (0, import_fs.readdirSync)(dir);
|
|
3242
|
+
} catch {
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
for (const entry of entries) {
|
|
3246
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3247
|
+
const full = (0, import_path.join)(dir, entry);
|
|
3248
|
+
try {
|
|
3249
|
+
const st = (0, import_fs.statSync)(full);
|
|
3250
|
+
if (st.isDirectory()) scanCssSelectorsInto(full, into);
|
|
3251
|
+
else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
|
|
3252
|
+
} catch {
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
function stripModifierPrefix(token) {
|
|
3257
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
3258
|
+
}
|
|
3179
3259
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3180
3260
|
function kbach(userConfigOrOptions) {
|
|
3181
3261
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3190,6 +3270,19 @@ function kbach(userConfigOrOptions) {
|
|
|
3190
3270
|
let mainCSSFile = null;
|
|
3191
3271
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3192
3272
|
const tokenCSS = /* @__PURE__ */ new Map();
|
|
3273
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
3274
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
3275
|
+
function checkUnknownToken(tok) {
|
|
3276
|
+
if (process2.env.NODE_ENV === "production" || warnedTokens.has(tok) || tok.startsWith("__")) return;
|
|
3277
|
+
const parsed = parseClass(tok);
|
|
3278
|
+
if (parsed && resolveUtility(parsed, cfg.theme) !== null) return;
|
|
3279
|
+
const base = stripModifierPrefix(tok);
|
|
3280
|
+
if (!base || projectCssClasses.has(base)) return;
|
|
3281
|
+
warnedTokens.add(tok);
|
|
3282
|
+
console.warn(
|
|
3283
|
+
`[kbach] "${tok}" isn't a Kbach utility and no matching CSS rule was found anywhere in the project (checked .css/.scss/.sass/.less files) \u2014 possible typo? If this is intentional (a third-party class, one defined via CSS-in-JS, etc.), this warning is safe to ignore.`
|
|
3284
|
+
);
|
|
3285
|
+
}
|
|
3193
3286
|
function processFile(filePath, code) {
|
|
3194
3287
|
const key = normPath(filePath);
|
|
3195
3288
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3198,9 +3291,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3198
3291
|
if (!tokenCSS.has(tok)) {
|
|
3199
3292
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3200
3293
|
}
|
|
3294
|
+
checkUnknownToken(tok);
|
|
3201
3295
|
}
|
|
3202
3296
|
fileTokens.set(key, tokens);
|
|
3203
3297
|
}
|
|
3298
|
+
function scanProjectCssSelectors() {
|
|
3299
|
+
for (const dir of includeDirs) scanCssSelectorsInto((0, import_path.join)(root, dir), projectCssClasses);
|
|
3300
|
+
}
|
|
3204
3301
|
function buildTokenCSSView() {
|
|
3205
3302
|
const active = /* @__PURE__ */ new Set();
|
|
3206
3303
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3224,6 +3321,12 @@ function kbach(userConfigOrOptions) {
|
|
|
3224
3321
|
enforce: "pre",
|
|
3225
3322
|
configResolved(resolved) {
|
|
3226
3323
|
root = resolved.root;
|
|
3324
|
+
const names = new Set(resolved.plugins.map((p) => p.name));
|
|
3325
|
+
if (names.has("react-router") && names.has("vite:react-refresh")) {
|
|
3326
|
+
console.warn(
|
|
3327
|
+
`[kbach] Detected both @vitejs/plugin-react and @react-router/dev's reactRouter() plugin active together. reactRouter() already includes its own JSX transform and Fast Refresh integration \u2014 having both crashes the page at runtime with "Identifier 'RefreshRuntime' has already been declared" before React hydrates, which looks like every class silently failing to style. Remove the react() plugin from vite.config.ts; jsxImportSource in tsconfig.json is enough on its own.`
|
|
3328
|
+
);
|
|
3329
|
+
}
|
|
3227
3330
|
},
|
|
3228
3331
|
// When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
|
|
3229
3332
|
// so styles come solely from the static stylesheet — no duplicate injection.
|
|
@@ -3239,11 +3342,17 @@ ${code}`,
|
|
|
3239
3342
|
},
|
|
3240
3343
|
buildStart() {
|
|
3241
3344
|
mainCSSFile = findKbachCSS(root);
|
|
3345
|
+
scanProjectCssSelectors();
|
|
3242
3346
|
initialScan();
|
|
3243
3347
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3244
3348
|
},
|
|
3245
3349
|
handleHotUpdate({ file, server }) {
|
|
3246
|
-
if (file.includes("node_modules")
|
|
3350
|
+
if (file.includes("node_modules")) return;
|
|
3351
|
+
if (CSS_FILE_RE.test(file)) {
|
|
3352
|
+
scanCssFileInto(file, projectCssClasses);
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
3247
3356
|
try {
|
|
3248
3357
|
processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
|
|
3249
3358
|
} catch {
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1905,6 +1905,33 @@ var RESOLVERS = {
|
|
|
1905
1905
|
const color = resolveColor(value, colors, isArbitrary);
|
|
1906
1906
|
return color ? { accentColor: color } : null;
|
|
1907
1907
|
},
|
|
1908
|
+
// ── SVG stroke/fill (web-only) ────────────────────────────────────────────
|
|
1909
|
+
// Native is intentionally excluded: react-native-svg's <Path>/<Circle>/etc.
|
|
1910
|
+
// take stroke/fill as component PROPS, not style entries, so there's no
|
|
1911
|
+
// reliable way to apply a resolved color through the `style` prop there.
|
|
1912
|
+
// `stroke-{n}` sets strokeWidth (a plain number, unlike a color) — same
|
|
1913
|
+
// color-first-then-numeric-fallback disambiguation `border` uses above.
|
|
1914
|
+
stroke: ({ value, isArbitrary }, { colors }) => {
|
|
1915
|
+
if (!getEffectiveIsWeb()) return null;
|
|
1916
|
+
if (!value) return null;
|
|
1917
|
+
if (value === "none") return { stroke: "none" };
|
|
1918
|
+
if (isArbitrary) {
|
|
1919
|
+
const w = toNativeValue(value);
|
|
1920
|
+
return typeof w === "number" ? { strokeWidth: w } : { stroke: value };
|
|
1921
|
+
}
|
|
1922
|
+
const color = resolveColor(value, colors, false);
|
|
1923
|
+
if (color) return { stroke: color };
|
|
1924
|
+
const n = parseFloat(value);
|
|
1925
|
+
return isNaN(n) ? null : { strokeWidth: n };
|
|
1926
|
+
},
|
|
1927
|
+
fill: ({ value, isArbitrary }, { colors }) => {
|
|
1928
|
+
if (!getEffectiveIsWeb()) return null;
|
|
1929
|
+
if (!value) return null;
|
|
1930
|
+
if (value === "none") return { fill: "none" };
|
|
1931
|
+
if (isArbitrary) return { fill: value };
|
|
1932
|
+
const color = resolveColor(value, colors, false);
|
|
1933
|
+
return color ? { fill: color } : null;
|
|
1934
|
+
},
|
|
1908
1935
|
// ── Flex ───────────────────────────────────────────────────────────────────
|
|
1909
1936
|
flex: ({ value, isArbitrary }, { flex }) => {
|
|
1910
1937
|
if (!value) return { display: "flex" };
|
|
@@ -2208,10 +2235,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2208
2235
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2209
2236
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2210
2237
|
}
|
|
2238
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2211
2239
|
function resolveUtility(parsed, theme) {
|
|
2212
2240
|
if (!parsed.value) {
|
|
2213
2241
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2214
2242
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2243
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2215
2244
|
}
|
|
2216
2245
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2217
2246
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2329,11 +2358,30 @@ function clearPluginModifiers() {
|
|
|
2329
2358
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2330
2359
|
_invalidate();
|
|
2331
2360
|
}
|
|
2361
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
2362
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
2363
|
+
function getNamedGroupPeerModifier(name) {
|
|
2364
|
+
const cached = _namedModifierCache.get(name);
|
|
2365
|
+
if (cached) return cached;
|
|
2366
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
2367
|
+
if (!m) return void 0;
|
|
2368
|
+
const [, kind, trigger, groupName] = m;
|
|
2369
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
2370
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
2371
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
2372
|
+
const def = {
|
|
2373
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
2374
|
+
jsBehavior: "css-only",
|
|
2375
|
+
forcesImportant: true
|
|
2376
|
+
};
|
|
2377
|
+
_namedModifierCache.set(name, def);
|
|
2378
|
+
return def;
|
|
2379
|
+
}
|
|
2332
2380
|
function getModifier(name) {
|
|
2333
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2381
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2334
2382
|
}
|
|
2335
2383
|
function isKnownModifier(name) {
|
|
2336
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2384
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2337
2385
|
}
|
|
2338
2386
|
|
|
2339
2387
|
// src/core/parser.ts
|
|
@@ -3142,6 +3190,38 @@ function scanDir(dir, onFile) {
|
|
|
3142
3190
|
}
|
|
3143
3191
|
}
|
|
3144
3192
|
}
|
|
3193
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
3194
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
3195
|
+
function scanCssFileInto(filePath, into) {
|
|
3196
|
+
try {
|
|
3197
|
+
const text = readFileSync(filePath, "utf-8");
|
|
3198
|
+
let m;
|
|
3199
|
+
CSS_CLASS_SELECTOR_RE.lastIndex = 0;
|
|
3200
|
+
while ((m = CSS_CLASS_SELECTOR_RE.exec(text)) !== null) into.add(m[1]);
|
|
3201
|
+
} catch {
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
function scanCssSelectorsInto(dir, into) {
|
|
3205
|
+
let entries;
|
|
3206
|
+
try {
|
|
3207
|
+
entries = readdirSync(dir);
|
|
3208
|
+
} catch {
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3211
|
+
for (const entry of entries) {
|
|
3212
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3213
|
+
const full = join(dir, entry);
|
|
3214
|
+
try {
|
|
3215
|
+
const st = statSync(full);
|
|
3216
|
+
if (st.isDirectory()) scanCssSelectorsInto(full, into);
|
|
3217
|
+
else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
|
|
3218
|
+
} catch {
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
function stripModifierPrefix(token) {
|
|
3223
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
3224
|
+
}
|
|
3145
3225
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3146
3226
|
function kbach(userConfigOrOptions) {
|
|
3147
3227
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3156,6 +3236,19 @@ function kbach(userConfigOrOptions) {
|
|
|
3156
3236
|
let mainCSSFile = null;
|
|
3157
3237
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3158
3238
|
const tokenCSS = /* @__PURE__ */ new Map();
|
|
3239
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
3240
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
3241
|
+
function checkUnknownToken(tok) {
|
|
3242
|
+
if (process2.env.NODE_ENV === "production" || warnedTokens.has(tok) || tok.startsWith("__")) return;
|
|
3243
|
+
const parsed = parseClass(tok);
|
|
3244
|
+
if (parsed && resolveUtility(parsed, cfg.theme) !== null) return;
|
|
3245
|
+
const base = stripModifierPrefix(tok);
|
|
3246
|
+
if (!base || projectCssClasses.has(base)) return;
|
|
3247
|
+
warnedTokens.add(tok);
|
|
3248
|
+
console.warn(
|
|
3249
|
+
`[kbach] "${tok}" isn't a Kbach utility and no matching CSS rule was found anywhere in the project (checked .css/.scss/.sass/.less files) \u2014 possible typo? If this is intentional (a third-party class, one defined via CSS-in-JS, etc.), this warning is safe to ignore.`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3159
3252
|
function processFile(filePath, code) {
|
|
3160
3253
|
const key = normPath(filePath);
|
|
3161
3254
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3164,9 +3257,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3164
3257
|
if (!tokenCSS.has(tok)) {
|
|
3165
3258
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3166
3259
|
}
|
|
3260
|
+
checkUnknownToken(tok);
|
|
3167
3261
|
}
|
|
3168
3262
|
fileTokens.set(key, tokens);
|
|
3169
3263
|
}
|
|
3264
|
+
function scanProjectCssSelectors() {
|
|
3265
|
+
for (const dir of includeDirs) scanCssSelectorsInto(join(root, dir), projectCssClasses);
|
|
3266
|
+
}
|
|
3170
3267
|
function buildTokenCSSView() {
|
|
3171
3268
|
const active = /* @__PURE__ */ new Set();
|
|
3172
3269
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3190,6 +3287,12 @@ function kbach(userConfigOrOptions) {
|
|
|
3190
3287
|
enforce: "pre",
|
|
3191
3288
|
configResolved(resolved) {
|
|
3192
3289
|
root = resolved.root;
|
|
3290
|
+
const names = new Set(resolved.plugins.map((p) => p.name));
|
|
3291
|
+
if (names.has("react-router") && names.has("vite:react-refresh")) {
|
|
3292
|
+
console.warn(
|
|
3293
|
+
`[kbach] Detected both @vitejs/plugin-react and @react-router/dev's reactRouter() plugin active together. reactRouter() already includes its own JSX transform and Fast Refresh integration \u2014 having both crashes the page at runtime with "Identifier 'RefreshRuntime' has already been declared" before React hydrates, which looks like every class silently failing to style. Remove the react() plugin from vite.config.ts; jsxImportSource in tsconfig.json is enough on its own.`
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3193
3296
|
},
|
|
3194
3297
|
// When any JS/TS file imports kbach.css, prepend a disableRuntimeCSS() call
|
|
3195
3298
|
// so styles come solely from the static stylesheet — no duplicate injection.
|
|
@@ -3205,11 +3308,17 @@ ${code}`,
|
|
|
3205
3308
|
},
|
|
3206
3309
|
buildStart() {
|
|
3207
3310
|
mainCSSFile = findKbachCSS(root);
|
|
3311
|
+
scanProjectCssSelectors();
|
|
3208
3312
|
initialScan();
|
|
3209
3313
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3210
3314
|
},
|
|
3211
3315
|
handleHotUpdate({ file, server }) {
|
|
3212
|
-
if (file.includes("node_modules")
|
|
3316
|
+
if (file.includes("node_modules")) return;
|
|
3317
|
+
if (CSS_FILE_RE.test(file)) {
|
|
3318
|
+
scanCssFileInto(file, projectCssClasses);
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
3213
3322
|
try {
|
|
3214
3323
|
processFile(file, readFileSync(file, "utf-8"));
|
|
3215
3324
|
} catch {
|
package/kbach-react.md
CHANGED
|
@@ -13,7 +13,7 @@ Package: `@kbach/react`
|
|
|
13
13
|
{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@kbach/react" } }
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
### vite.config.ts
|
|
16
|
+
### vite.config.ts (plain Vite only — skip @vitejs/plugin-react if a meta-framework already provides its own Vite/React plugin, e.g. React Router's reactRouter(); see below)
|
|
17
17
|
Requires `vite` and `@vitejs/plugin-react` as dev dependencies — not installed by `@kbach/react` itself: `npm install -D vite @vitejs/plugin-react`.
|
|
18
18
|
```ts
|
|
19
19
|
import { defineConfig } from 'vite';
|
|
@@ -50,7 +50,13 @@ import { ThemeProvider } from '@kbach/react';
|
|
|
50
50
|
tsconfig `jsxImportSource` setup above applies as-is. No Vite plugin for Next.js (webpack/Turbopack) — falls back to runtime CSS injection, which only runs client-side, so expect a brief flash of unstyled content on first paint before hydration. `@kbach/react`'s compiled output ships its own `"use client"` directive, so App Router Server Components can use `className`, `styled()`, hooks, and `<ThemeProvider>` directly, with no manual client wrapper needed.
|
|
51
51
|
|
|
52
52
|
### React Router
|
|
53
|
-
Framework mode (v7
|
|
53
|
+
Framework mode (v7+, SSR): do NOT add `@vitejs/plugin-react` — `@react-router/dev`'s `reactRouter()` Vite plugin already includes its own JSX transform + Fast Refresh integration. Adding both makes each inject its own Fast Refresh preamble into the same module, crashing the page (`Identifier 'RefreshRuntime' has already been declared`) before React hydrates — every class on the page silently fails to style because the app never mounts. tsconfig `jsxImportSource` alone is enough:
|
|
54
|
+
```ts
|
|
55
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
56
|
+
import { kbach } from '@kbach/react/vite'; // omit if not using static CSS
|
|
57
|
+
export default { plugins: [kbach(), reactRouter()] };
|
|
58
|
+
```
|
|
59
|
+
Default scan dirs include `app/`. Library mode (client-only, no meta-framework Vite plugin involved) needs no special handling beyond the standard Vite setup above.
|
|
54
60
|
|
|
55
61
|
---
|
|
56
62
|
|
|
@@ -61,6 +67,7 @@ The `kbach()` Vite plugin scans your source files and writes generated CSS betwe
|
|
|
61
67
|
- Runs on `buildStart` (initial load) and on every HMR file change
|
|
62
68
|
- Stateless: rescans all files fresh every time — removed classes are immediately evicted
|
|
63
69
|
- Output is grouped by category with CSS custom properties for theme colors
|
|
70
|
+
- Also indexes every `.css`/`.scss`/`.sass`/`.less` file the same `include` dirs cover, and warns (`console.warn`, dev-server terminal, not the browser) for any class that's neither a real Kbach utility NOR defined anywhere in those stylesheets — a likely typo. Classes intentionally handled elsewhere (CSS Modules, styled-components, a third-party component's own class) are recognized once anything in the project literally defines `.that-class-name` and stay silent.
|
|
64
71
|
|
|
65
72
|
```ts
|
|
66
73
|
// vite.config.ts
|
|
@@ -247,6 +254,18 @@ Mark a parent with `group`, then use `group-hover:` etc. on children.
|
|
|
247
254
|
| `peer-hover:` | Previous sibling `.peer` is hovered |
|
|
248
255
|
| `peer-focus:` | Previous sibling `.peer` is focused |
|
|
249
256
|
|
|
257
|
+
**Named groups/peers** — nested groups need names to avoid an inner element
|
|
258
|
+
reacting to the wrong (nearest) ancestor: `group/{name}` + `group-hover/{name}:`,
|
|
259
|
+
same for `peer/{name}` + `peer-hover/{name}:`/`peer-focus/{name}:`.
|
|
260
|
+
```jsx
|
|
261
|
+
<div className="group/card">
|
|
262
|
+
<div className="group/icon">
|
|
263
|
+
<span className="group-hover/icon:opacity-100" />
|
|
264
|
+
</div>
|
|
265
|
+
<span className="group-hover/card:underline" />
|
|
266
|
+
</div>
|
|
267
|
+
```
|
|
268
|
+
|
|
250
269
|
### Pseudo-element modifiers
|
|
251
270
|
```jsx
|
|
252
271
|
<div className="before:content-['*'] before:text-red-6 relative" />
|
|
@@ -630,6 +649,8 @@ aspect-auto / aspect-square / aspect-video / aspect-[4/3]
|
|
|
630
649
|
columns-{n} / columns-auto / columns-{size}
|
|
631
650
|
caret-{color} / caret-auto / caret-transparent
|
|
632
651
|
accent-{color} / accent-auto
|
|
652
|
+
stroke-{color} / stroke-{n} / stroke-none (web only, SVG)
|
|
653
|
+
fill-{color} / fill-none (web only, SVG)
|
|
633
654
|
mix-blend-{mode}
|
|
634
655
|
bg-blend-{mode}
|
|
635
656
|
will-change-auto/scroll/contents/transform
|