@kbach/react 0.6.4 → 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 +13 -0
- package/dist/{chunk-SVPKCLOI.mjs → chunk-BAUO36PM.mjs} +37 -6
- package/dist/{chunk-EU7RK3K4.mjs → chunk-FXDK5Z6V.mjs} +1 -1
- package/dist/index.js +56 -8
- package/dist/index.mjs +20 -3
- package/dist/jsx-dev-runtime.js +37 -6
- package/dist/jsx-dev-runtime.mjs +2 -2
- package/dist/jsx-runtime.js +37 -6
- package/dist/jsx-runtime.mjs +2 -2
- package/dist/vite-plugin.js +79 -3
- package/dist/vite-plugin.mjs +79 -3
- package/kbach-react.md +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ import './kbach.css';
|
|
|
88
88
|
|
|
89
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.
|
|
90
90
|
|
|
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.
|
|
92
|
+
|
|
91
93
|
## Next.js
|
|
92
94
|
|
|
93
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.
|
|
@@ -298,6 +300,17 @@ Negated: `not-hover:`, `not-focus:`, `not-pressed:`, `not-active:`, `not-disable
|
|
|
298
300
|
<p className="peer-focus:text-blue-6" />
|
|
299
301
|
```
|
|
300
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
|
+
|
|
301
314
|
### Responsive
|
|
302
315
|
|
|
303
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)];
|
|
@@ -2331,10 +2350,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2331
2350
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2332
2351
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2333
2352
|
}
|
|
2353
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2334
2354
|
function resolveUtility(parsed, theme) {
|
|
2335
2355
|
if (!parsed.value) {
|
|
2336
2356
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2337
2357
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2358
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2338
2359
|
}
|
|
2339
2360
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2340
2361
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2352,7 +2373,7 @@ function getPluginStandaloneMap() {
|
|
|
2352
2373
|
return PLUGIN_STANDALONE;
|
|
2353
2374
|
}
|
|
2354
2375
|
function isKnownUtility(utility) {
|
|
2355
|
-
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);
|
|
2356
2377
|
}
|
|
2357
2378
|
var _sortedPrefixes = null;
|
|
2358
2379
|
var _standaloneNames = null;
|
|
@@ -3631,9 +3652,19 @@ var InteractiveWrapper = forwardRef(
|
|
|
3631
3652
|
style: finalStyle,
|
|
3632
3653
|
...!isNative && className ? { className } : {},
|
|
3633
3654
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3634
|
-
//
|
|
3635
|
-
// (
|
|
3636
|
-
|
|
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 },
|
|
3637
3668
|
onMouseEnter: handleMouseEnter,
|
|
3638
3669
|
onMouseLeave: handleMouseLeave,
|
|
3639
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)];
|
|
@@ -2450,10 +2469,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2450
2469
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2451
2470
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2452
2471
|
}
|
|
2472
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2453
2473
|
function resolveUtility(parsed, theme) {
|
|
2454
2474
|
if (!parsed.value) {
|
|
2455
2475
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2456
2476
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2477
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2457
2478
|
}
|
|
2458
2479
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2459
2480
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2471,7 +2492,7 @@ function getPluginStandaloneMap() {
|
|
|
2471
2492
|
return PLUGIN_STANDALONE;
|
|
2472
2493
|
}
|
|
2473
2494
|
function isKnownUtility(utility) {
|
|
2474
|
-
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);
|
|
2475
2496
|
}
|
|
2476
2497
|
var _sortedPrefixes = null;
|
|
2477
2498
|
var _standaloneNames = null;
|
|
@@ -3917,8 +3938,25 @@ function styled(Component, baseClasses = "") {
|
|
|
3917
3938
|
// Only attach state-tracking handlers when interactive modifiers are present.
|
|
3918
3939
|
// Always forward user-provided handlers to avoid silently swallowing them.
|
|
3919
3940
|
// onPressIn/onPressOut are forwarded on web too (not just native) since
|
|
3920
|
-
// react-native-web components rely on receiving them directly
|
|
3921
|
-
|
|
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 },
|
|
3922
3960
|
onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
|
|
3923
3961
|
onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
|
|
3924
3962
|
onFocus: hasInteractive ? handleFocus : onFocus,
|
|
@@ -4039,9 +4077,19 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
|
|
|
4039
4077
|
style: finalStyle,
|
|
4040
4078
|
...!isNative && className ? { className } : {},
|
|
4041
4079
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
4042
|
-
//
|
|
4043
|
-
// (
|
|
4044
|
-
|
|
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 },
|
|
4045
4093
|
onMouseEnter: handleMouseEnter,
|
|
4046
4094
|
onMouseLeave: handleMouseLeave,
|
|
4047
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)];
|
|
@@ -2409,10 +2428,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2409
2428
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2410
2429
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2411
2430
|
}
|
|
2431
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2412
2432
|
function resolveUtility(parsed, theme) {
|
|
2413
2433
|
if (!parsed.value) {
|
|
2414
2434
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2415
2435
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2436
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2416
2437
|
}
|
|
2417
2438
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2418
2439
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2430,7 +2451,7 @@ function getPluginStandaloneMap() {
|
|
|
2430
2451
|
return PLUGIN_STANDALONE;
|
|
2431
2452
|
}
|
|
2432
2453
|
function isKnownUtility(utility) {
|
|
2433
|
-
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);
|
|
2434
2455
|
}
|
|
2435
2456
|
var _sortedPrefixes = null;
|
|
2436
2457
|
var _standaloneNames = null;
|
|
@@ -3328,9 +3349,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3328
3349
|
style: finalStyle,
|
|
3329
3350
|
...!isNative && className ? { className } : {},
|
|
3330
3351
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3331
|
-
//
|
|
3332
|
-
// (
|
|
3333
|
-
|
|
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 },
|
|
3334
3365
|
onMouseEnter: handleMouseEnter,
|
|
3335
3366
|
onMouseLeave: handleMouseLeave,
|
|
3336
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)];
|
|
@@ -2410,10 +2429,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2410
2429
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2411
2430
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2412
2431
|
}
|
|
2432
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2413
2433
|
function resolveUtility(parsed, theme) {
|
|
2414
2434
|
if (!parsed.value) {
|
|
2415
2435
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2416
2436
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2437
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2417
2438
|
}
|
|
2418
2439
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2419
2440
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2431,7 +2452,7 @@ function getPluginStandaloneMap() {
|
|
|
2431
2452
|
return PLUGIN_STANDALONE;
|
|
2432
2453
|
}
|
|
2433
2454
|
function isKnownUtility(utility) {
|
|
2434
|
-
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);
|
|
2435
2456
|
}
|
|
2436
2457
|
var _sortedPrefixes = null;
|
|
2437
2458
|
var _standaloneNames = null;
|
|
@@ -3329,9 +3350,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3329
3350
|
style: finalStyle,
|
|
3330
3351
|
...!isNative && className ? { className } : {},
|
|
3331
3352
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3332
|
-
//
|
|
3333
|
-
// (
|
|
3334
|
-
|
|
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 },
|
|
3335
3366
|
onMouseEnter: handleMouseEnter,
|
|
3336
3367
|
onMouseLeave: handleMouseLeave,
|
|
3337
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
|
@@ -2269,10 +2269,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2269
2269
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2270
2270
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2271
2271
|
}
|
|
2272
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2272
2273
|
function resolveUtility(parsed, theme) {
|
|
2273
2274
|
if (!parsed.value) {
|
|
2274
2275
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2275
2276
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2277
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2276
2278
|
}
|
|
2277
2279
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2278
2280
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2390,11 +2392,30 @@ function clearPluginModifiers() {
|
|
|
2390
2392
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2391
2393
|
_invalidate();
|
|
2392
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
|
+
}
|
|
2393
2414
|
function getModifier(name) {
|
|
2394
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2415
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2395
2416
|
}
|
|
2396
2417
|
function isKnownModifier(name) {
|
|
2397
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2418
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2398
2419
|
}
|
|
2399
2420
|
|
|
2400
2421
|
// src/core/parser.ts
|
|
@@ -3203,6 +3224,38 @@ function scanDir(dir, onFile) {
|
|
|
3203
3224
|
}
|
|
3204
3225
|
}
|
|
3205
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
|
+
}
|
|
3206
3259
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3207
3260
|
function kbach(userConfigOrOptions) {
|
|
3208
3261
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3217,6 +3270,19 @@ function kbach(userConfigOrOptions) {
|
|
|
3217
3270
|
let mainCSSFile = null;
|
|
3218
3271
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3219
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
|
+
}
|
|
3220
3286
|
function processFile(filePath, code) {
|
|
3221
3287
|
const key = normPath(filePath);
|
|
3222
3288
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3225,9 +3291,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3225
3291
|
if (!tokenCSS.has(tok)) {
|
|
3226
3292
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3227
3293
|
}
|
|
3294
|
+
checkUnknownToken(tok);
|
|
3228
3295
|
}
|
|
3229
3296
|
fileTokens.set(key, tokens);
|
|
3230
3297
|
}
|
|
3298
|
+
function scanProjectCssSelectors() {
|
|
3299
|
+
for (const dir of includeDirs) scanCssSelectorsInto((0, import_path.join)(root, dir), projectCssClasses);
|
|
3300
|
+
}
|
|
3231
3301
|
function buildTokenCSSView() {
|
|
3232
3302
|
const active = /* @__PURE__ */ new Set();
|
|
3233
3303
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3272,11 +3342,17 @@ ${code}`,
|
|
|
3272
3342
|
},
|
|
3273
3343
|
buildStart() {
|
|
3274
3344
|
mainCSSFile = findKbachCSS(root);
|
|
3345
|
+
scanProjectCssSelectors();
|
|
3275
3346
|
initialScan();
|
|
3276
3347
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3277
3348
|
},
|
|
3278
3349
|
handleHotUpdate({ file, server }) {
|
|
3279
|
-
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;
|
|
3280
3356
|
try {
|
|
3281
3357
|
processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
|
|
3282
3358
|
} catch {
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -2235,10 +2235,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2235
2235
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2236
2236
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2237
2237
|
}
|
|
2238
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2238
2239
|
function resolveUtility(parsed, theme) {
|
|
2239
2240
|
if (!parsed.value) {
|
|
2240
2241
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2241
2242
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2243
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2242
2244
|
}
|
|
2243
2245
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2244
2246
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2356,11 +2358,30 @@ function clearPluginModifiers() {
|
|
|
2356
2358
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2357
2359
|
_invalidate();
|
|
2358
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
|
+
}
|
|
2359
2380
|
function getModifier(name) {
|
|
2360
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2381
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2361
2382
|
}
|
|
2362
2383
|
function isKnownModifier(name) {
|
|
2363
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2384
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2364
2385
|
}
|
|
2365
2386
|
|
|
2366
2387
|
// src/core/parser.ts
|
|
@@ -3169,6 +3190,38 @@ function scanDir(dir, onFile) {
|
|
|
3169
3190
|
}
|
|
3170
3191
|
}
|
|
3171
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
|
+
}
|
|
3172
3225
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3173
3226
|
function kbach(userConfigOrOptions) {
|
|
3174
3227
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3183,6 +3236,19 @@ function kbach(userConfigOrOptions) {
|
|
|
3183
3236
|
let mainCSSFile = null;
|
|
3184
3237
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3185
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
|
+
}
|
|
3186
3252
|
function processFile(filePath, code) {
|
|
3187
3253
|
const key = normPath(filePath);
|
|
3188
3254
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3191,9 +3257,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3191
3257
|
if (!tokenCSS.has(tok)) {
|
|
3192
3258
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3193
3259
|
}
|
|
3260
|
+
checkUnknownToken(tok);
|
|
3194
3261
|
}
|
|
3195
3262
|
fileTokens.set(key, tokens);
|
|
3196
3263
|
}
|
|
3264
|
+
function scanProjectCssSelectors() {
|
|
3265
|
+
for (const dir of includeDirs) scanCssSelectorsInto(join(root, dir), projectCssClasses);
|
|
3266
|
+
}
|
|
3197
3267
|
function buildTokenCSSView() {
|
|
3198
3268
|
const active = /* @__PURE__ */ new Set();
|
|
3199
3269
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3238,11 +3308,17 @@ ${code}`,
|
|
|
3238
3308
|
},
|
|
3239
3309
|
buildStart() {
|
|
3240
3310
|
mainCSSFile = findKbachCSS(root);
|
|
3311
|
+
scanProjectCssSelectors();
|
|
3241
3312
|
initialScan();
|
|
3242
3313
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3243
3314
|
},
|
|
3244
3315
|
handleHotUpdate({ file, server }) {
|
|
3245
|
-
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;
|
|
3246
3322
|
try {
|
|
3247
3323
|
processFile(file, readFileSync(file, "utf-8"));
|
|
3248
3324
|
} catch {
|
package/kbach-react.md
CHANGED
|
@@ -67,6 +67,7 @@ The `kbach()` Vite plugin scans your source files and writes generated CSS betwe
|
|
|
67
67
|
- Runs on `buildStart` (initial load) and on every HMR file change
|
|
68
68
|
- Stateless: rescans all files fresh every time — removed classes are immediately evicted
|
|
69
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.
|
|
70
71
|
|
|
71
72
|
```ts
|
|
72
73
|
// vite.config.ts
|
|
@@ -253,6 +254,18 @@ Mark a parent with `group`, then use `group-hover:` etc. on children.
|
|
|
253
254
|
| `peer-hover:` | Previous sibling `.peer` is hovered |
|
|
254
255
|
| `peer-focus:` | Previous sibling `.peer` is focused |
|
|
255
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
|
+
|
|
256
269
|
### Pseudo-element modifiers
|
|
257
270
|
```jsx
|
|
258
271
|
<div className="before:content-['*'] before:text-red-6 relative" />
|