@kbach/react 0.6.4 → 0.6.6
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-EU7RK3K4.mjs → chunk-5YN6PIXQ.mjs} +1 -1
- package/dist/{chunk-SVPKCLOI.mjs → chunk-LXOMVLQI.mjs} +50 -10
- package/dist/index.js +69 -12
- package/dist/index.mjs +20 -3
- package/dist/jsx-dev-runtime.js +50 -10
- package/dist/jsx-dev-runtime.mjs +2 -2
- package/dist/jsx-runtime.js +50 -10
- package/dist/jsx-runtime.mjs +2 -2
- package/dist/vite-plugin.js +131 -12
- package/dist/vite-plugin.mjs +132 -13
- 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. Each warning includes a `file:line:column` location that most terminals (VS Code's integrated terminal included) turn into a clickable link straight to that class.
|
|
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 |
|
|
@@ -565,6 +565,15 @@ function escapeCSSSelector(cls) {
|
|
|
565
565
|
return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
// src/core/devWarn.ts
|
|
569
|
+
function kbachWarn(message) {
|
|
570
|
+
console.warn(
|
|
571
|
+
"%c[kbach]%c " + message,
|
|
572
|
+
"color:#8b5cf6;font-weight:700",
|
|
573
|
+
"color:inherit;font-weight:400"
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
568
577
|
// src/core/registry.ts
|
|
569
578
|
var BUILTIN_MODIFIERS = {
|
|
570
579
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -640,7 +649,7 @@ function _invalidate() {
|
|
|
640
649
|
}
|
|
641
650
|
function registerModifier(name, def) {
|
|
642
651
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
643
|
-
|
|
652
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
644
653
|
return;
|
|
645
654
|
}
|
|
646
655
|
_pluginModifiers[name] = def;
|
|
@@ -650,11 +659,30 @@ function clearPluginModifiers() {
|
|
|
650
659
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
651
660
|
_invalidate();
|
|
652
661
|
}
|
|
662
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
663
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
664
|
+
function getNamedGroupPeerModifier(name) {
|
|
665
|
+
const cached = _namedModifierCache.get(name);
|
|
666
|
+
if (cached) return cached;
|
|
667
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
668
|
+
if (!m) return void 0;
|
|
669
|
+
const [, kind, trigger, groupName] = m;
|
|
670
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
671
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
672
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
673
|
+
const def = {
|
|
674
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
675
|
+
jsBehavior: "css-only",
|
|
676
|
+
forcesImportant: true
|
|
677
|
+
};
|
|
678
|
+
_namedModifierCache.set(name, def);
|
|
679
|
+
return def;
|
|
680
|
+
}
|
|
653
681
|
function getModifier(name) {
|
|
654
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
682
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
655
683
|
}
|
|
656
684
|
function isKnownModifier(name) {
|
|
657
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
685
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
658
686
|
}
|
|
659
687
|
function _allEntries() {
|
|
660
688
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2331,10 +2359,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2331
2359
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2332
2360
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2333
2361
|
}
|
|
2362
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2334
2363
|
function resolveUtility(parsed, theme) {
|
|
2335
2364
|
if (!parsed.value) {
|
|
2336
2365
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2337
2366
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2367
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2338
2368
|
}
|
|
2339
2369
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2340
2370
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2352,7 +2382,7 @@ function getPluginStandaloneMap() {
|
|
|
2352
2382
|
return PLUGIN_STANDALONE;
|
|
2353
2383
|
}
|
|
2354
2384
|
function isKnownUtility(utility) {
|
|
2355
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2385
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2356
2386
|
}
|
|
2357
2387
|
var _sortedPrefixes = null;
|
|
2358
2388
|
var _standaloneNames = null;
|
|
@@ -2413,7 +2443,7 @@ function parseClass(className) {
|
|
|
2413
2443
|
}
|
|
2414
2444
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2415
2445
|
if (process.env.NODE_ENV !== "production") {
|
|
2416
|
-
|
|
2446
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2417
2447
|
}
|
|
2418
2448
|
}
|
|
2419
2449
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2880,7 +2910,7 @@ function resolve(classString, theme, darkMode = "attribute") {
|
|
|
2880
2910
|
const styles = resolveUtility(parsed, theme);
|
|
2881
2911
|
if (!styles) {
|
|
2882
2912
|
if (process.env.NODE_ENV !== "production" && !parsed.isArbitrary && !isKnownUtility(parsed.utility) && !parsed.original.startsWith("__")) {
|
|
2883
|
-
|
|
2913
|
+
kbachWarn(`Unknown class "${parsed.original}"`);
|
|
2884
2914
|
}
|
|
2885
2915
|
continue;
|
|
2886
2916
|
}
|
|
@@ -3035,7 +3065,7 @@ function buildConfig(userConfig) {
|
|
|
3035
3065
|
for (const [key, value] of Object.entries(ext)) {
|
|
3036
3066
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3037
3067
|
if (process.env.NODE_ENV !== "production") {
|
|
3038
|
-
|
|
3068
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
3039
3069
|
}
|
|
3040
3070
|
continue;
|
|
3041
3071
|
}
|
|
@@ -3631,9 +3661,19 @@ var InteractiveWrapper = forwardRef(
|
|
|
3631
3661
|
style: finalStyle,
|
|
3632
3662
|
...!isNative && className ? { className } : {},
|
|
3633
3663
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3634
|
-
//
|
|
3635
|
-
// (
|
|
3636
|
-
|
|
3664
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3665
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3666
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3667
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3668
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3669
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3670
|
+
...!isNative ? {
|
|
3671
|
+
onPointerDown: handlePointerDown,
|
|
3672
|
+
onPointerUp: handlePointerUp,
|
|
3673
|
+
onPointerLeave: handlePointerLeave,
|
|
3674
|
+
onPointerCancel: handlePointerCancel,
|
|
3675
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3676
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3637
3677
|
onMouseEnter: handleMouseEnter,
|
|
3638
3678
|
onMouseLeave: handleMouseLeave,
|
|
3639
3679
|
onFocus: handleFocus,
|
package/dist/index.js
CHANGED
|
@@ -690,6 +690,15 @@ function escapeCSSSelector(cls) {
|
|
|
690
690
|
return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
|
|
691
691
|
}
|
|
692
692
|
|
|
693
|
+
// src/core/devWarn.ts
|
|
694
|
+
function kbachWarn(message) {
|
|
695
|
+
console.warn(
|
|
696
|
+
"%c[kbach]%c " + message,
|
|
697
|
+
"color:#8b5cf6;font-weight:700",
|
|
698
|
+
"color:inherit;font-weight:400"
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
693
702
|
// src/core/registry.ts
|
|
694
703
|
var BUILTIN_MODIFIERS = {
|
|
695
704
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -765,7 +774,7 @@ function _invalidate() {
|
|
|
765
774
|
}
|
|
766
775
|
function registerModifier(name, def) {
|
|
767
776
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
768
|
-
|
|
777
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
769
778
|
return;
|
|
770
779
|
}
|
|
771
780
|
_pluginModifiers[name] = def;
|
|
@@ -775,11 +784,30 @@ function clearPluginModifiers() {
|
|
|
775
784
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
776
785
|
_invalidate();
|
|
777
786
|
}
|
|
787
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
788
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
789
|
+
function getNamedGroupPeerModifier(name) {
|
|
790
|
+
const cached = _namedModifierCache.get(name);
|
|
791
|
+
if (cached) return cached;
|
|
792
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
793
|
+
if (!m) return void 0;
|
|
794
|
+
const [, kind, trigger, groupName] = m;
|
|
795
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
796
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
797
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
798
|
+
const def = {
|
|
799
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
800
|
+
jsBehavior: "css-only",
|
|
801
|
+
forcesImportant: true
|
|
802
|
+
};
|
|
803
|
+
_namedModifierCache.set(name, def);
|
|
804
|
+
return def;
|
|
805
|
+
}
|
|
778
806
|
function getModifier(name) {
|
|
779
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
807
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
780
808
|
}
|
|
781
809
|
function isKnownModifier(name) {
|
|
782
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
810
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
783
811
|
}
|
|
784
812
|
function _allEntries() {
|
|
785
813
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2450,10 +2478,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2450
2478
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2451
2479
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2452
2480
|
}
|
|
2481
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2453
2482
|
function resolveUtility(parsed, theme) {
|
|
2454
2483
|
if (!parsed.value) {
|
|
2455
2484
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2456
2485
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2486
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2457
2487
|
}
|
|
2458
2488
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2459
2489
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2471,7 +2501,7 @@ function getPluginStandaloneMap() {
|
|
|
2471
2501
|
return PLUGIN_STANDALONE;
|
|
2472
2502
|
}
|
|
2473
2503
|
function isKnownUtility(utility) {
|
|
2474
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2504
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2475
2505
|
}
|
|
2476
2506
|
var _sortedPrefixes = null;
|
|
2477
2507
|
var _standaloneNames = null;
|
|
@@ -2532,7 +2562,7 @@ function parseClass(className) {
|
|
|
2532
2562
|
}
|
|
2533
2563
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2534
2564
|
if (process.env.NODE_ENV !== "production") {
|
|
2535
|
-
|
|
2565
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2536
2566
|
}
|
|
2537
2567
|
}
|
|
2538
2568
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2958,7 +2988,7 @@ function resolve(classString, theme, darkMode = "attribute") {
|
|
|
2958
2988
|
const styles = resolveUtility(parsed, theme);
|
|
2959
2989
|
if (!styles) {
|
|
2960
2990
|
if (process.env.NODE_ENV !== "production" && !parsed.isArbitrary && !isKnownUtility(parsed.utility) && !parsed.original.startsWith("__")) {
|
|
2961
|
-
|
|
2991
|
+
kbachWarn(`Unknown class "${parsed.original}"`);
|
|
2962
2992
|
}
|
|
2963
2993
|
continue;
|
|
2964
2994
|
}
|
|
@@ -3113,7 +3143,7 @@ function buildConfig(userConfig) {
|
|
|
3113
3143
|
for (const [key, value] of Object.entries(ext)) {
|
|
3114
3144
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3115
3145
|
if (process.env.NODE_ENV !== "production") {
|
|
3116
|
-
|
|
3146
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
3117
3147
|
}
|
|
3118
3148
|
continue;
|
|
3119
3149
|
}
|
|
@@ -3917,8 +3947,25 @@ function styled(Component, baseClasses = "") {
|
|
|
3917
3947
|
// Only attach state-tracking handlers when interactive modifiers are present.
|
|
3918
3948
|
// Always forward user-provided handlers to avoid silently swallowing them.
|
|
3919
3949
|
// onPressIn/onPressOut are forwarded on web too (not just native) since
|
|
3920
|
-
// react-native-web components rely on receiving them directly
|
|
3921
|
-
|
|
3950
|
+
// react-native-web components rely on receiving them directly — but only
|
|
3951
|
+
// when effectiveComponent is an actual component, never a literal HTML tag
|
|
3952
|
+
// string ('div', 'a', …). React DOM warns "Unknown event handler property"
|
|
3953
|
+
// for onPressIn/onPressOut on a host element even when the value is
|
|
3954
|
+
// undefined — the check is on the prop KEY being present at all, not its
|
|
3955
|
+
// value — so the key must be omitted entirely for string components.
|
|
3956
|
+
...isWeb ? hasInteractive ? {
|
|
3957
|
+
onPointerDown: handlePointerDown,
|
|
3958
|
+
onPointerUp: handlePointerUp,
|
|
3959
|
+
onPointerLeave: handlePointerLeave,
|
|
3960
|
+
onPointerCancel: handlePointerCancel,
|
|
3961
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
3962
|
+
} : {
|
|
3963
|
+
onPointerDown,
|
|
3964
|
+
onPointerUp,
|
|
3965
|
+
onPointerLeave,
|
|
3966
|
+
onPointerCancel,
|
|
3967
|
+
...typeof effectiveComponent !== "string" ? { onPressIn, onPressOut } : {}
|
|
3968
|
+
} : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
|
|
3922
3969
|
onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
|
|
3923
3970
|
onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
|
|
3924
3971
|
onFocus: hasInteractive ? handleFocus : onFocus,
|
|
@@ -4039,9 +4086,19 @@ var InteractiveWrapper = (0, import_react8.forwardRef)(
|
|
|
4039
4086
|
style: finalStyle,
|
|
4040
4087
|
...!isNative && className ? { className } : {},
|
|
4041
4088
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
4042
|
-
//
|
|
4043
|
-
// (
|
|
4044
|
-
|
|
4089
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
4090
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
4091
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
4092
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
4093
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
4094
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
4095
|
+
...!isNative ? {
|
|
4096
|
+
onPointerDown: handlePointerDown,
|
|
4097
|
+
onPointerUp: handlePointerUp,
|
|
4098
|
+
onPointerLeave: handlePointerLeave,
|
|
4099
|
+
onPointerCancel: handlePointerCancel,
|
|
4100
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
4101
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
4045
4102
|
onMouseEnter: handleMouseEnter,
|
|
4046
4103
|
onMouseLeave: handleMouseLeave,
|
|
4047
4104
|
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-LXOMVLQI.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
|
@@ -643,6 +643,15 @@ function escapeCSSSelector(cls) {
|
|
|
643
643
|
return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
// src/core/devWarn.ts
|
|
647
|
+
function kbachWarn(message) {
|
|
648
|
+
console.warn(
|
|
649
|
+
"%c[kbach]%c " + message,
|
|
650
|
+
"color:#8b5cf6;font-weight:700",
|
|
651
|
+
"color:inherit;font-weight:400"
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
646
655
|
// src/core/registry.ts
|
|
647
656
|
var BUILTIN_MODIFIERS = {
|
|
648
657
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -718,7 +727,7 @@ function _invalidate() {
|
|
|
718
727
|
}
|
|
719
728
|
function registerModifier(name, def) {
|
|
720
729
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
721
|
-
|
|
730
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
722
731
|
return;
|
|
723
732
|
}
|
|
724
733
|
_pluginModifiers[name] = def;
|
|
@@ -728,11 +737,30 @@ function clearPluginModifiers() {
|
|
|
728
737
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
729
738
|
_invalidate();
|
|
730
739
|
}
|
|
740
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
741
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
742
|
+
function getNamedGroupPeerModifier(name) {
|
|
743
|
+
const cached = _namedModifierCache.get(name);
|
|
744
|
+
if (cached) return cached;
|
|
745
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
746
|
+
if (!m) return void 0;
|
|
747
|
+
const [, kind, trigger, groupName] = m;
|
|
748
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
749
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
750
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
751
|
+
const def = {
|
|
752
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
753
|
+
jsBehavior: "css-only",
|
|
754
|
+
forcesImportant: true
|
|
755
|
+
};
|
|
756
|
+
_namedModifierCache.set(name, def);
|
|
757
|
+
return def;
|
|
758
|
+
}
|
|
731
759
|
function getModifier(name) {
|
|
732
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
760
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
733
761
|
}
|
|
734
762
|
function isKnownModifier(name) {
|
|
735
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
763
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
736
764
|
}
|
|
737
765
|
function _allEntries() {
|
|
738
766
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2409,10 +2437,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2409
2437
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2410
2438
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2411
2439
|
}
|
|
2440
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2412
2441
|
function resolveUtility(parsed, theme) {
|
|
2413
2442
|
if (!parsed.value) {
|
|
2414
2443
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2415
2444
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2445
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2416
2446
|
}
|
|
2417
2447
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2418
2448
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2430,7 +2460,7 @@ function getPluginStandaloneMap() {
|
|
|
2430
2460
|
return PLUGIN_STANDALONE;
|
|
2431
2461
|
}
|
|
2432
2462
|
function isKnownUtility(utility) {
|
|
2433
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2463
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2434
2464
|
}
|
|
2435
2465
|
var _sortedPrefixes = null;
|
|
2436
2466
|
var _standaloneNames = null;
|
|
@@ -2491,7 +2521,7 @@ function parseClass(className) {
|
|
|
2491
2521
|
}
|
|
2492
2522
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2493
2523
|
if (process.env.NODE_ENV !== "production") {
|
|
2494
|
-
|
|
2524
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2495
2525
|
}
|
|
2496
2526
|
}
|
|
2497
2527
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2902,7 +2932,7 @@ function resolve(classString, theme, darkMode = "attribute") {
|
|
|
2902
2932
|
const styles = resolveUtility(parsed, theme);
|
|
2903
2933
|
if (!styles) {
|
|
2904
2934
|
if (process.env.NODE_ENV !== "production" && !parsed.isArbitrary && !isKnownUtility(parsed.utility) && !parsed.original.startsWith("__")) {
|
|
2905
|
-
|
|
2935
|
+
kbachWarn(`Unknown class "${parsed.original}"`);
|
|
2906
2936
|
}
|
|
2907
2937
|
continue;
|
|
2908
2938
|
}
|
|
@@ -3046,7 +3076,7 @@ function buildConfig(userConfig) {
|
|
|
3046
3076
|
for (const [key, value] of Object.entries(ext)) {
|
|
3047
3077
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3048
3078
|
if (process.env.NODE_ENV !== "production") {
|
|
3049
|
-
|
|
3079
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
3050
3080
|
}
|
|
3051
3081
|
continue;
|
|
3052
3082
|
}
|
|
@@ -3328,9 +3358,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3328
3358
|
style: finalStyle,
|
|
3329
3359
|
...!isNative && className ? { className } : {},
|
|
3330
3360
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3331
|
-
//
|
|
3332
|
-
// (
|
|
3333
|
-
|
|
3361
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3362
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3363
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3364
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3365
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3366
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3367
|
+
...!isNative ? {
|
|
3368
|
+
onPointerDown: handlePointerDown,
|
|
3369
|
+
onPointerUp: handlePointerUp,
|
|
3370
|
+
onPointerLeave: handlePointerLeave,
|
|
3371
|
+
onPointerCancel: handlePointerCancel,
|
|
3372
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3373
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3334
3374
|
onMouseEnter: handleMouseEnter,
|
|
3335
3375
|
onMouseLeave: handleMouseLeave,
|
|
3336
3376
|
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-5YN6PIXQ.mjs";
|
|
7
|
+
import "./chunk-LXOMVLQI.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
|
@@ -644,6 +644,15 @@ function escapeCSSSelector(cls) {
|
|
|
644
644
|
return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
+
// src/core/devWarn.ts
|
|
648
|
+
function kbachWarn(message) {
|
|
649
|
+
console.warn(
|
|
650
|
+
"%c[kbach]%c " + message,
|
|
651
|
+
"color:#8b5cf6;font-weight:700",
|
|
652
|
+
"color:inherit;font-weight:400"
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
647
656
|
// src/core/registry.ts
|
|
648
657
|
var BUILTIN_MODIFIERS = {
|
|
649
658
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -719,7 +728,7 @@ function _invalidate() {
|
|
|
719
728
|
}
|
|
720
729
|
function registerModifier(name, def) {
|
|
721
730
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
722
|
-
|
|
731
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
723
732
|
return;
|
|
724
733
|
}
|
|
725
734
|
_pluginModifiers[name] = def;
|
|
@@ -729,11 +738,30 @@ function clearPluginModifiers() {
|
|
|
729
738
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
730
739
|
_invalidate();
|
|
731
740
|
}
|
|
741
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
742
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
743
|
+
function getNamedGroupPeerModifier(name) {
|
|
744
|
+
const cached = _namedModifierCache.get(name);
|
|
745
|
+
if (cached) return cached;
|
|
746
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
747
|
+
if (!m) return void 0;
|
|
748
|
+
const [, kind, trigger, groupName] = m;
|
|
749
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
750
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
751
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
752
|
+
const def = {
|
|
753
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
754
|
+
jsBehavior: "css-only",
|
|
755
|
+
forcesImportant: true
|
|
756
|
+
};
|
|
757
|
+
_namedModifierCache.set(name, def);
|
|
758
|
+
return def;
|
|
759
|
+
}
|
|
732
760
|
function getModifier(name) {
|
|
733
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
761
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
734
762
|
}
|
|
735
763
|
function isKnownModifier(name) {
|
|
736
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
764
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
737
765
|
}
|
|
738
766
|
function _allEntries() {
|
|
739
767
|
return [...Object.entries(BUILTIN_MODIFIERS), ...Object.entries(_pluginModifiers)];
|
|
@@ -2410,10 +2438,12 @@ for (const [utility, prop] of Object.entries(PADDING_MARGIN_PROPS)) {
|
|
|
2410
2438
|
for (const [utility, [widthProp, colorProp]] of Object.entries(BORDER_SIDE_PROPS)) {
|
|
2411
2439
|
RESOLVERS[utility] = makeBorderSideResolver(widthProp, colorProp);
|
|
2412
2440
|
}
|
|
2441
|
+
var NAMED_GROUP_PEER_MARKER_RE = /^(group|peer)\/.+$/;
|
|
2413
2442
|
function resolveUtility(parsed, theme) {
|
|
2414
2443
|
if (!parsed.value) {
|
|
2415
2444
|
if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
|
|
2416
2445
|
if (parsed.utility in getStandalone()) return getStandalone()[parsed.utility] ?? null;
|
|
2446
|
+
if (NAMED_GROUP_PEER_MARKER_RE.test(parsed.utility)) return getEffectiveIsWeb() ? {} : null;
|
|
2417
2447
|
}
|
|
2418
2448
|
const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
|
|
2419
2449
|
if (resolver) return resolver(parsed, theme);
|
|
@@ -2431,7 +2461,7 @@ function getPluginStandaloneMap() {
|
|
|
2431
2461
|
return PLUGIN_STANDALONE;
|
|
2432
2462
|
}
|
|
2433
2463
|
function isKnownUtility(utility) {
|
|
2434
|
-
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS;
|
|
2464
|
+
return utility in getStandalone() || utility in RESOLVERS || utility in PLUGIN_STANDALONE || utility in PLUGIN_RESOLVERS || NAMED_GROUP_PEER_MARKER_RE.test(utility);
|
|
2435
2465
|
}
|
|
2436
2466
|
var _sortedPrefixes = null;
|
|
2437
2467
|
var _standaloneNames = null;
|
|
@@ -2492,7 +2522,7 @@ function parseClass(className) {
|
|
|
2492
2522
|
}
|
|
2493
2523
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2494
2524
|
if (process.env.NODE_ENV !== "production") {
|
|
2495
|
-
|
|
2525
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2496
2526
|
}
|
|
2497
2527
|
}
|
|
2498
2528
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2903,7 +2933,7 @@ function resolve(classString, theme, darkMode = "attribute") {
|
|
|
2903
2933
|
const styles = resolveUtility(parsed, theme);
|
|
2904
2934
|
if (!styles) {
|
|
2905
2935
|
if (process.env.NODE_ENV !== "production" && !parsed.isArbitrary && !isKnownUtility(parsed.utility) && !parsed.original.startsWith("__")) {
|
|
2906
|
-
|
|
2936
|
+
kbachWarn(`Unknown class "${parsed.original}"`);
|
|
2907
2937
|
}
|
|
2908
2938
|
continue;
|
|
2909
2939
|
}
|
|
@@ -3047,7 +3077,7 @@ function buildConfig(userConfig) {
|
|
|
3047
3077
|
for (const [key, value] of Object.entries(ext)) {
|
|
3048
3078
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3049
3079
|
if (process.env.NODE_ENV !== "production") {
|
|
3050
|
-
|
|
3080
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
3051
3081
|
}
|
|
3052
3082
|
continue;
|
|
3053
3083
|
}
|
|
@@ -3329,9 +3359,19 @@ var InteractiveWrapper = (0, import_react2.forwardRef)(
|
|
|
3329
3359
|
style: finalStyle,
|
|
3330
3360
|
...!isNative && className ? { className } : {},
|
|
3331
3361
|
// On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch),
|
|
3332
|
-
//
|
|
3333
|
-
// (
|
|
3334
|
-
|
|
3362
|
+
// and onPressIn/onPressOut are ALSO forwarded — but only when Component is an actual
|
|
3363
|
+
// component (react-native-web components accept both prop names), never when it's a
|
|
3364
|
+
// literal HTML tag string ('div', 'a', …). React DOM warns "Unknown event handler
|
|
3365
|
+
// property" for onPressIn/onPressOut on a host element even when the value is
|
|
3366
|
+
// undefined — the check is on the prop KEY being present at all, not its value — so
|
|
3367
|
+
// the key must be omitted entirely for string components, not just set to undefined.
|
|
3368
|
+
...!isNative ? {
|
|
3369
|
+
onPointerDown: handlePointerDown,
|
|
3370
|
+
onPointerUp: handlePointerUp,
|
|
3371
|
+
onPointerLeave: handlePointerLeave,
|
|
3372
|
+
onPointerCancel: handlePointerCancel,
|
|
3373
|
+
...typeof Component !== "string" ? { onPressIn, onPressOut } : {}
|
|
3374
|
+
} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
|
|
3335
3375
|
onMouseEnter: handleMouseEnter,
|
|
3336
3376
|
onMouseLeave: handleMouseLeave,
|
|
3337
3377
|
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-5YN6PIXQ.mjs";
|
|
8
8
|
import {
|
|
9
9
|
registerWebElement
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LXOMVLQI.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);
|
|
@@ -2305,6 +2307,15 @@ function getBuiltinStandaloneNames() {
|
|
|
2305
2307
|
return _standaloneNames;
|
|
2306
2308
|
}
|
|
2307
2309
|
|
|
2310
|
+
// src/core/devWarn.ts
|
|
2311
|
+
function kbachWarn(message) {
|
|
2312
|
+
console.warn(
|
|
2313
|
+
"%c[kbach]%c " + message,
|
|
2314
|
+
"color:#8b5cf6;font-weight:700",
|
|
2315
|
+
"color:inherit;font-weight:400"
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2308
2319
|
// src/core/registry.ts
|
|
2309
2320
|
var BUILTIN_MODIFIERS = {
|
|
2310
2321
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -2380,7 +2391,7 @@ function _invalidate() {
|
|
|
2380
2391
|
}
|
|
2381
2392
|
function registerModifier(name, def) {
|
|
2382
2393
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
2383
|
-
|
|
2394
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
2384
2395
|
return;
|
|
2385
2396
|
}
|
|
2386
2397
|
_pluginModifiers[name] = def;
|
|
@@ -2390,11 +2401,30 @@ function clearPluginModifiers() {
|
|
|
2390
2401
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2391
2402
|
_invalidate();
|
|
2392
2403
|
}
|
|
2404
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
2405
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
2406
|
+
function getNamedGroupPeerModifier(name) {
|
|
2407
|
+
const cached = _namedModifierCache.get(name);
|
|
2408
|
+
if (cached) return cached;
|
|
2409
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
2410
|
+
if (!m) return void 0;
|
|
2411
|
+
const [, kind, trigger, groupName] = m;
|
|
2412
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
2413
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
2414
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
2415
|
+
const def = {
|
|
2416
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
2417
|
+
jsBehavior: "css-only",
|
|
2418
|
+
forcesImportant: true
|
|
2419
|
+
};
|
|
2420
|
+
_namedModifierCache.set(name, def);
|
|
2421
|
+
return def;
|
|
2422
|
+
}
|
|
2393
2423
|
function getModifier(name) {
|
|
2394
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2424
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2395
2425
|
}
|
|
2396
2426
|
function isKnownModifier(name) {
|
|
2397
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2427
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2398
2428
|
}
|
|
2399
2429
|
|
|
2400
2430
|
// src/core/parser.ts
|
|
@@ -2440,7 +2470,7 @@ function parseClass(className) {
|
|
|
2440
2470
|
}
|
|
2441
2471
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2442
2472
|
if (process.env.NODE_ENV !== "production") {
|
|
2443
|
-
|
|
2473
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2444
2474
|
}
|
|
2445
2475
|
}
|
|
2446
2476
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2876,7 +2906,7 @@ function buildConfig(userConfig) {
|
|
|
2876
2906
|
for (const [key, value] of Object.entries(ext)) {
|
|
2877
2907
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2878
2908
|
if (process.env.NODE_ENV !== "production") {
|
|
2879
|
-
|
|
2909
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
2880
2910
|
}
|
|
2881
2911
|
continue;
|
|
2882
2912
|
}
|
|
@@ -2975,6 +3005,39 @@ function normPath(p) {
|
|
|
2975
3005
|
return process2.platform === "win32" ? fwd.toLowerCase() : fwd;
|
|
2976
3006
|
}
|
|
2977
3007
|
setCSSGenMode(true);
|
|
3008
|
+
var useColor = !!process2.stdout?.isTTY && !process2.env.NO_COLOR;
|
|
3009
|
+
var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
3010
|
+
var purple = (s) => paint("35", s);
|
|
3011
|
+
var yellow = (s) => paint("33", s);
|
|
3012
|
+
var dim = (s) => paint("2", s);
|
|
3013
|
+
var bold = (s) => paint("1", s);
|
|
3014
|
+
var TAG = () => `${bold(purple("[kbach]"))}`;
|
|
3015
|
+
function warn(message) {
|
|
3016
|
+
console.warn(`${TAG()} ${yellow(message)}`);
|
|
3017
|
+
}
|
|
3018
|
+
function warnBlock(header, ...lines) {
|
|
3019
|
+
console.warn([`${TAG()} ${yellow(header)}`, ...lines].join("\n"));
|
|
3020
|
+
}
|
|
3021
|
+
function findLineCol(code, needle) {
|
|
3022
|
+
const index = code.indexOf(needle);
|
|
3023
|
+
if (index === -1) return null;
|
|
3024
|
+
let line = 1;
|
|
3025
|
+
let lastNewline = -1;
|
|
3026
|
+
for (let i = 0; i < index; i++) {
|
|
3027
|
+
if (code.charCodeAt(i) === 10) {
|
|
3028
|
+
line++;
|
|
3029
|
+
lastNewline = i;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
return { line, column: index - lastNewline };
|
|
3033
|
+
}
|
|
3034
|
+
function warnAt(message, root, filePath, code, needle) {
|
|
3035
|
+
const pos = findLineCol(code, needle);
|
|
3036
|
+
const relPath = (0, import_path.relative)(root, filePath).replace(/\\/g, "/");
|
|
3037
|
+
const location = pos ? `${relPath}:${pos.line}:${pos.column}` : relPath;
|
|
3038
|
+
console.warn(`${TAG()} ${yellow(message)}
|
|
3039
|
+
${dim("at")} ${location}`);
|
|
3040
|
+
}
|
|
2978
3041
|
var KBACH_START = "/* kbach:start */";
|
|
2979
3042
|
var KBACH_END = "/* kbach:end */";
|
|
2980
3043
|
var GROUPS = [
|
|
@@ -3097,8 +3160,9 @@ function findKbachCSS(root) {
|
|
|
3097
3160
|
if (found.length === 0) return null;
|
|
3098
3161
|
found.sort((a, b) => a.split(/[\\/]/g).length - b.split(/[\\/]/g).length);
|
|
3099
3162
|
if (found.length > 1) {
|
|
3100
|
-
|
|
3101
|
-
|
|
3163
|
+
warnBlock(
|
|
3164
|
+
`Multiple kbach.css found, using the shallowest \u2014 remove the rest:`,
|
|
3165
|
+
...found.map((f) => dim(` ${f}`))
|
|
3102
3166
|
);
|
|
3103
3167
|
}
|
|
3104
3168
|
return found[0];
|
|
@@ -3187,7 +3251,7 @@ function scanDir(dir, onFile) {
|
|
|
3187
3251
|
entries = (0, import_fs.readdirSync)(dir);
|
|
3188
3252
|
} catch (err) {
|
|
3189
3253
|
if (err.code !== "ENOENT") {
|
|
3190
|
-
|
|
3254
|
+
warn(`Can't read ${dir}: ${err.message}`);
|
|
3191
3255
|
}
|
|
3192
3256
|
return;
|
|
3193
3257
|
}
|
|
@@ -3199,10 +3263,42 @@ function scanDir(dir, onFile) {
|
|
|
3199
3263
|
if (st.isDirectory()) scanDir(full, onFile);
|
|
3200
3264
|
else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
|
|
3201
3265
|
} catch (err) {
|
|
3202
|
-
|
|
3266
|
+
warn(`Can't process ${full}: ${err.message}`);
|
|
3203
3267
|
}
|
|
3204
3268
|
}
|
|
3205
3269
|
}
|
|
3270
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
3271
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
3272
|
+
function scanCssFileInto(filePath, into) {
|
|
3273
|
+
try {
|
|
3274
|
+
const text = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
3275
|
+
let m;
|
|
3276
|
+
CSS_CLASS_SELECTOR_RE.lastIndex = 0;
|
|
3277
|
+
while ((m = CSS_CLASS_SELECTOR_RE.exec(text)) !== null) into.add(m[1]);
|
|
3278
|
+
} catch {
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
function scanCssSelectorsInto(dir, into) {
|
|
3282
|
+
let entries;
|
|
3283
|
+
try {
|
|
3284
|
+
entries = (0, import_fs.readdirSync)(dir);
|
|
3285
|
+
} catch {
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
for (const entry of entries) {
|
|
3289
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3290
|
+
const full = (0, import_path.join)(dir, entry);
|
|
3291
|
+
try {
|
|
3292
|
+
const st = (0, import_fs.statSync)(full);
|
|
3293
|
+
if (st.isDirectory()) scanCssSelectorsInto(full, into);
|
|
3294
|
+
else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
|
|
3295
|
+
} catch {
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
function stripModifierPrefix(token) {
|
|
3300
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
3301
|
+
}
|
|
3206
3302
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3207
3303
|
function kbach(userConfigOrOptions) {
|
|
3208
3304
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3217,6 +3313,17 @@ function kbach(userConfigOrOptions) {
|
|
|
3217
3313
|
let mainCSSFile = null;
|
|
3218
3314
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3219
3315
|
const tokenCSS = /* @__PURE__ */ new Map();
|
|
3316
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
3317
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
3318
|
+
function checkUnknownToken(tok, filePath, code) {
|
|
3319
|
+
if (process2.env.NODE_ENV === "production" || warnedTokens.has(tok) || tok.startsWith("__")) return;
|
|
3320
|
+
const parsed = parseClass(tok);
|
|
3321
|
+
if (parsed && resolveUtility(parsed, cfg.theme) !== null) return;
|
|
3322
|
+
const base = stripModifierPrefix(tok);
|
|
3323
|
+
if (!base || projectCssClasses.has(base)) return;
|
|
3324
|
+
warnedTokens.add(tok);
|
|
3325
|
+
warnAt(`Unknown class "${tok}" \u2014 no Kbach utility or project CSS rule matches it. Typo?`, root, filePath, code, tok);
|
|
3326
|
+
}
|
|
3220
3327
|
function processFile(filePath, code) {
|
|
3221
3328
|
const key = normPath(filePath);
|
|
3222
3329
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3225,9 +3332,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3225
3332
|
if (!tokenCSS.has(tok)) {
|
|
3226
3333
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3227
3334
|
}
|
|
3335
|
+
checkUnknownToken(tok, filePath, code);
|
|
3228
3336
|
}
|
|
3229
3337
|
fileTokens.set(key, tokens);
|
|
3230
3338
|
}
|
|
3339
|
+
function scanProjectCssSelectors() {
|
|
3340
|
+
for (const dir of includeDirs) scanCssSelectorsInto((0, import_path.join)(root, dir), projectCssClasses);
|
|
3341
|
+
}
|
|
3231
3342
|
function buildTokenCSSView() {
|
|
3232
3343
|
const active = /* @__PURE__ */ new Set();
|
|
3233
3344
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3253,8 +3364,10 @@ function kbach(userConfigOrOptions) {
|
|
|
3253
3364
|
root = resolved.root;
|
|
3254
3365
|
const names = new Set(resolved.plugins.map((p) => p.name));
|
|
3255
3366
|
if (names.has("react-router") && names.has("vite:react-refresh")) {
|
|
3256
|
-
|
|
3257
|
-
|
|
3367
|
+
warnBlock(
|
|
3368
|
+
"@vitejs/plugin-react + reactRouter() conflict \u2014 page will crash before styles apply.",
|
|
3369
|
+
dim(' Both inject Fast Refresh ("RefreshRuntime already declared").'),
|
|
3370
|
+
dim(" Fix: remove react() from vite.config.ts \u2014 tsconfig jsxImportSource is enough.")
|
|
3258
3371
|
);
|
|
3259
3372
|
}
|
|
3260
3373
|
},
|
|
@@ -3272,11 +3385,17 @@ ${code}`,
|
|
|
3272
3385
|
},
|
|
3273
3386
|
buildStart() {
|
|
3274
3387
|
mainCSSFile = findKbachCSS(root);
|
|
3388
|
+
scanProjectCssSelectors();
|
|
3275
3389
|
initialScan();
|
|
3276
3390
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3277
3391
|
},
|
|
3278
3392
|
handleHotUpdate({ file, server }) {
|
|
3279
|
-
if (file.includes("node_modules")
|
|
3393
|
+
if (file.includes("node_modules")) return;
|
|
3394
|
+
if (CSS_FILE_RE.test(file)) {
|
|
3395
|
+
scanCssFileInto(file, projectCssClasses);
|
|
3396
|
+
return;
|
|
3397
|
+
}
|
|
3398
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
3280
3399
|
try {
|
|
3281
3400
|
processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
|
|
3282
3401
|
} catch {
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/vite-plugin.ts
|
|
2
2
|
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { join, relative } from "path";
|
|
4
4
|
import processImport from "process";
|
|
5
5
|
|
|
6
6
|
// src/core/platform.ts
|
|
@@ -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);
|
|
@@ -2271,6 +2273,15 @@ function getBuiltinStandaloneNames() {
|
|
|
2271
2273
|
return _standaloneNames;
|
|
2272
2274
|
}
|
|
2273
2275
|
|
|
2276
|
+
// src/core/devWarn.ts
|
|
2277
|
+
function kbachWarn(message) {
|
|
2278
|
+
console.warn(
|
|
2279
|
+
"%c[kbach]%c " + message,
|
|
2280
|
+
"color:#8b5cf6;font-weight:700",
|
|
2281
|
+
"color:inherit;font-weight:400"
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2274
2285
|
// src/core/registry.ts
|
|
2275
2286
|
var BUILTIN_MODIFIERS = {
|
|
2276
2287
|
// ── Mode ─────────────────────────────────────────────────────────────────
|
|
@@ -2346,7 +2357,7 @@ function _invalidate() {
|
|
|
2346
2357
|
}
|
|
2347
2358
|
function registerModifier(name, def) {
|
|
2348
2359
|
if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
|
|
2349
|
-
|
|
2360
|
+
kbachWarn(`"${name}" is built-in \u2014 pick another modifier name`);
|
|
2350
2361
|
return;
|
|
2351
2362
|
}
|
|
2352
2363
|
_pluginModifiers[name] = def;
|
|
@@ -2356,11 +2367,30 @@ function clearPluginModifiers() {
|
|
|
2356
2367
|
for (const k of Object.keys(_pluginModifiers)) delete _pluginModifiers[k];
|
|
2357
2368
|
_invalidate();
|
|
2358
2369
|
}
|
|
2370
|
+
var NAMED_GROUP_PEER_RE = /^(group|peer)-(hover|focus)\/(.+)$/;
|
|
2371
|
+
var _namedModifierCache = /* @__PURE__ */ new Map();
|
|
2372
|
+
function getNamedGroupPeerModifier(name) {
|
|
2373
|
+
const cached = _namedModifierCache.get(name);
|
|
2374
|
+
if (cached) return cached;
|
|
2375
|
+
const m = NAMED_GROUP_PEER_RE.exec(name);
|
|
2376
|
+
if (!m) return void 0;
|
|
2377
|
+
const [, kind, trigger, groupName] = m;
|
|
2378
|
+
const escapedName = escapeCSSSelector(groupName);
|
|
2379
|
+
const pseudo = trigger === "hover" ? ":hover" : ":focus";
|
|
2380
|
+
const combinator = kind === "group" ? " " : " ~ ";
|
|
2381
|
+
const def = {
|
|
2382
|
+
ancestorSelector: `.${kind}\\/${escapedName}${pseudo}${combinator}`,
|
|
2383
|
+
jsBehavior: "css-only",
|
|
2384
|
+
forcesImportant: true
|
|
2385
|
+
};
|
|
2386
|
+
_namedModifierCache.set(name, def);
|
|
2387
|
+
return def;
|
|
2388
|
+
}
|
|
2359
2389
|
function getModifier(name) {
|
|
2360
|
-
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name];
|
|
2390
|
+
return BUILTIN_MODIFIERS[name] ?? _pluginModifiers[name] ?? getNamedGroupPeerModifier(name);
|
|
2361
2391
|
}
|
|
2362
2392
|
function isKnownModifier(name) {
|
|
2363
|
-
return name in BUILTIN_MODIFIERS || name in _pluginModifiers;
|
|
2393
|
+
return name in BUILTIN_MODIFIERS || name in _pluginModifiers || NAMED_GROUP_PEER_RE.test(name);
|
|
2364
2394
|
}
|
|
2365
2395
|
|
|
2366
2396
|
// src/core/parser.ts
|
|
@@ -2406,7 +2436,7 @@ function parseClass(className) {
|
|
|
2406
2436
|
}
|
|
2407
2437
|
if (remaining.endsWith("]") && bracketStart === -1) {
|
|
2408
2438
|
if (process.env.NODE_ENV !== "production") {
|
|
2409
|
-
|
|
2439
|
+
kbachWarn(`Unbalanced brackets: "${trimmed}"`);
|
|
2410
2440
|
}
|
|
2411
2441
|
}
|
|
2412
2442
|
if (remaining.startsWith("[") && remaining.endsWith("]")) {
|
|
@@ -2842,7 +2872,7 @@ function buildConfig(userConfig) {
|
|
|
2842
2872
|
for (const [key, value] of Object.entries(ext)) {
|
|
2843
2873
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2844
2874
|
if (process.env.NODE_ENV !== "production") {
|
|
2845
|
-
|
|
2875
|
+
kbachWarn(`extend.${key} should be an object, got ${typeof value} \u2014 skipped`);
|
|
2846
2876
|
}
|
|
2847
2877
|
continue;
|
|
2848
2878
|
}
|
|
@@ -2941,6 +2971,39 @@ function normPath(p) {
|
|
|
2941
2971
|
return process2.platform === "win32" ? fwd.toLowerCase() : fwd;
|
|
2942
2972
|
}
|
|
2943
2973
|
setCSSGenMode(true);
|
|
2974
|
+
var useColor = !!process2.stdout?.isTTY && !process2.env.NO_COLOR;
|
|
2975
|
+
var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
2976
|
+
var purple = (s) => paint("35", s);
|
|
2977
|
+
var yellow = (s) => paint("33", s);
|
|
2978
|
+
var dim = (s) => paint("2", s);
|
|
2979
|
+
var bold = (s) => paint("1", s);
|
|
2980
|
+
var TAG = () => `${bold(purple("[kbach]"))}`;
|
|
2981
|
+
function warn(message) {
|
|
2982
|
+
console.warn(`${TAG()} ${yellow(message)}`);
|
|
2983
|
+
}
|
|
2984
|
+
function warnBlock(header, ...lines) {
|
|
2985
|
+
console.warn([`${TAG()} ${yellow(header)}`, ...lines].join("\n"));
|
|
2986
|
+
}
|
|
2987
|
+
function findLineCol(code, needle) {
|
|
2988
|
+
const index = code.indexOf(needle);
|
|
2989
|
+
if (index === -1) return null;
|
|
2990
|
+
let line = 1;
|
|
2991
|
+
let lastNewline = -1;
|
|
2992
|
+
for (let i = 0; i < index; i++) {
|
|
2993
|
+
if (code.charCodeAt(i) === 10) {
|
|
2994
|
+
line++;
|
|
2995
|
+
lastNewline = i;
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
return { line, column: index - lastNewline };
|
|
2999
|
+
}
|
|
3000
|
+
function warnAt(message, root, filePath, code, needle) {
|
|
3001
|
+
const pos = findLineCol(code, needle);
|
|
3002
|
+
const relPath = relative(root, filePath).replace(/\\/g, "/");
|
|
3003
|
+
const location = pos ? `${relPath}:${pos.line}:${pos.column}` : relPath;
|
|
3004
|
+
console.warn(`${TAG()} ${yellow(message)}
|
|
3005
|
+
${dim("at")} ${location}`);
|
|
3006
|
+
}
|
|
2944
3007
|
var KBACH_START = "/* kbach:start */";
|
|
2945
3008
|
var KBACH_END = "/* kbach:end */";
|
|
2946
3009
|
var GROUPS = [
|
|
@@ -3063,8 +3126,9 @@ function findKbachCSS(root) {
|
|
|
3063
3126
|
if (found.length === 0) return null;
|
|
3064
3127
|
found.sort((a, b) => a.split(/[\\/]/g).length - b.split(/[\\/]/g).length);
|
|
3065
3128
|
if (found.length > 1) {
|
|
3066
|
-
|
|
3067
|
-
|
|
3129
|
+
warnBlock(
|
|
3130
|
+
`Multiple kbach.css found, using the shallowest \u2014 remove the rest:`,
|
|
3131
|
+
...found.map((f) => dim(` ${f}`))
|
|
3068
3132
|
);
|
|
3069
3133
|
}
|
|
3070
3134
|
return found[0];
|
|
@@ -3153,7 +3217,7 @@ function scanDir(dir, onFile) {
|
|
|
3153
3217
|
entries = readdirSync(dir);
|
|
3154
3218
|
} catch (err) {
|
|
3155
3219
|
if (err.code !== "ENOENT") {
|
|
3156
|
-
|
|
3220
|
+
warn(`Can't read ${dir}: ${err.message}`);
|
|
3157
3221
|
}
|
|
3158
3222
|
return;
|
|
3159
3223
|
}
|
|
@@ -3165,10 +3229,42 @@ function scanDir(dir, onFile) {
|
|
|
3165
3229
|
if (st.isDirectory()) scanDir(full, onFile);
|
|
3166
3230
|
else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
|
|
3167
3231
|
} catch (err) {
|
|
3168
|
-
|
|
3232
|
+
warn(`Can't process ${full}: ${err.message}`);
|
|
3169
3233
|
}
|
|
3170
3234
|
}
|
|
3171
3235
|
}
|
|
3236
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
3237
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
3238
|
+
function scanCssFileInto(filePath, into) {
|
|
3239
|
+
try {
|
|
3240
|
+
const text = readFileSync(filePath, "utf-8");
|
|
3241
|
+
let m;
|
|
3242
|
+
CSS_CLASS_SELECTOR_RE.lastIndex = 0;
|
|
3243
|
+
while ((m = CSS_CLASS_SELECTOR_RE.exec(text)) !== null) into.add(m[1]);
|
|
3244
|
+
} catch {
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
function scanCssSelectorsInto(dir, into) {
|
|
3248
|
+
let entries;
|
|
3249
|
+
try {
|
|
3250
|
+
entries = readdirSync(dir);
|
|
3251
|
+
} catch {
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
for (const entry of entries) {
|
|
3255
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3256
|
+
const full = join(dir, entry);
|
|
3257
|
+
try {
|
|
3258
|
+
const st = statSync(full);
|
|
3259
|
+
if (st.isDirectory()) scanCssSelectorsInto(full, into);
|
|
3260
|
+
else if (CSS_FILE_RE.test(entry)) scanCssFileInto(full, into);
|
|
3261
|
+
} catch {
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
function stripModifierPrefix(token) {
|
|
3266
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
3267
|
+
}
|
|
3172
3268
|
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
|
|
3173
3269
|
function kbach(userConfigOrOptions) {
|
|
3174
3270
|
const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
|
|
@@ -3183,6 +3279,17 @@ function kbach(userConfigOrOptions) {
|
|
|
3183
3279
|
let mainCSSFile = null;
|
|
3184
3280
|
const fileTokens = /* @__PURE__ */ new Map();
|
|
3185
3281
|
const tokenCSS = /* @__PURE__ */ new Map();
|
|
3282
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
3283
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
3284
|
+
function checkUnknownToken(tok, filePath, code) {
|
|
3285
|
+
if (process2.env.NODE_ENV === "production" || warnedTokens.has(tok) || tok.startsWith("__")) return;
|
|
3286
|
+
const parsed = parseClass(tok);
|
|
3287
|
+
if (parsed && resolveUtility(parsed, cfg.theme) !== null) return;
|
|
3288
|
+
const base = stripModifierPrefix(tok);
|
|
3289
|
+
if (!base || projectCssClasses.has(base)) return;
|
|
3290
|
+
warnedTokens.add(tok);
|
|
3291
|
+
warnAt(`Unknown class "${tok}" \u2014 no Kbach utility or project CSS rule matches it. Typo?`, root, filePath, code, tok);
|
|
3292
|
+
}
|
|
3186
3293
|
function processFile(filePath, code) {
|
|
3187
3294
|
const key = normPath(filePath);
|
|
3188
3295
|
const tokens = /* @__PURE__ */ new Set();
|
|
@@ -3191,9 +3298,13 @@ function kbach(userConfigOrOptions) {
|
|
|
3191
3298
|
if (!tokenCSS.has(tok)) {
|
|
3192
3299
|
tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
|
|
3193
3300
|
}
|
|
3301
|
+
checkUnknownToken(tok, filePath, code);
|
|
3194
3302
|
}
|
|
3195
3303
|
fileTokens.set(key, tokens);
|
|
3196
3304
|
}
|
|
3305
|
+
function scanProjectCssSelectors() {
|
|
3306
|
+
for (const dir of includeDirs) scanCssSelectorsInto(join(root, dir), projectCssClasses);
|
|
3307
|
+
}
|
|
3197
3308
|
function buildTokenCSSView() {
|
|
3198
3309
|
const active = /* @__PURE__ */ new Set();
|
|
3199
3310
|
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
@@ -3219,8 +3330,10 @@ function kbach(userConfigOrOptions) {
|
|
|
3219
3330
|
root = resolved.root;
|
|
3220
3331
|
const names = new Set(resolved.plugins.map((p) => p.name));
|
|
3221
3332
|
if (names.has("react-router") && names.has("vite:react-refresh")) {
|
|
3222
|
-
|
|
3223
|
-
|
|
3333
|
+
warnBlock(
|
|
3334
|
+
"@vitejs/plugin-react + reactRouter() conflict \u2014 page will crash before styles apply.",
|
|
3335
|
+
dim(' Both inject Fast Refresh ("RefreshRuntime already declared").'),
|
|
3336
|
+
dim(" Fix: remove react() from vite.config.ts \u2014 tsconfig jsxImportSource is enough.")
|
|
3224
3337
|
);
|
|
3225
3338
|
}
|
|
3226
3339
|
},
|
|
@@ -3238,11 +3351,17 @@ ${code}`,
|
|
|
3238
3351
|
},
|
|
3239
3352
|
buildStart() {
|
|
3240
3353
|
mainCSSFile = findKbachCSS(root);
|
|
3354
|
+
scanProjectCssSelectors();
|
|
3241
3355
|
initialScan();
|
|
3242
3356
|
if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
|
|
3243
3357
|
},
|
|
3244
3358
|
handleHotUpdate({ file, server }) {
|
|
3245
|
-
if (file.includes("node_modules")
|
|
3359
|
+
if (file.includes("node_modules")) return;
|
|
3360
|
+
if (CSS_FILE_RE.test(file)) {
|
|
3361
|
+
scanCssFileInto(file, projectCssClasses);
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) return;
|
|
3246
3365
|
try {
|
|
3247
3366
|
processFile(file, readFileSync(file, "utf-8"));
|
|
3248
3367
|
} 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. Each warning prints a `file:line:column` location that terminals with clickable-link support (VS Code's included) turn into a jump-to-that-class link.
|
|
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" />
|